Linux内核初探

可装载内核模块(LKM)

Linux内核采用的是宏内核架构,一切的系统服务都需要由内核来提供,新服务的提供往往意味着要重新编译整个内核,于是可装载内核模块的出现大大提高了内核的可拓展性和可维护性。下面是一些基本的概念:

  • 可装载内核模块(LKM)是一段可以动态加载进内核的代码

  • 在Linux内核源码树以外来开发并编译一个模块,称为树外开发

  • 之所以提供模块机制,是因为Linux内核本身是一个单内核,单内核由于所有内容都集成在一起,效率很高,但可扩展性和可维护性较差,模块机制可弥补这一缺陷。

模块源代码 helloworld.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>

//内核模块初始化函数
static int __init verf1sh_init(void)
{
printk("Hello world from kernel space\n");
return 0;
}

//内核模块退出函数
static void __exit verf1sh_exit(void)
{
printk("Goodbye world leaving kernel space\n");
}

module_init(verf1sh_init);
module_exit(verf1sh_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("verf1sh");

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Makefile文件注意:假如前面的.c文件起名为first.c,那么这里的Makefile文件中的.o文
#件就要起名为first.o 只有root用户才能加载和卸载模块
obj-m:=helloworld.o #产生helloworld模块的目标文件
#目标文件 文件 要与模块名字相同
CURRENT_PATH:=$(shell pwd) #模块所在的当前路径
LINUX_KERNEL:=$(shell uname -r) #linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)

all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
#[Tab] 内核的路径 当前目录编译完放哪 表明编译的是内核模块

clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理模块

编译模块

1
make

装载模块

1
sudo insmod helloworld.ko

查看装载的模块

1
lsmod

卸载模块

1
sudo rmmod helloworld

虚拟地址到物理地址

模块源代码 paging_lowmem.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/sched.h>
#include <linux/export.h>
#include <linux/delay.h>


static unsigned long cr0,cr3;

static unsigned long vaddr = 0;


static void get_pgtable_macro(void)
{
cr0 = read_cr0();
cr3 = read_cr3_pa();

printk("cr0 = 0x%lx, cr3 = 0x%lx\n",cr0,cr3);

printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);
printk("P4D_SHIFT = %d\n",P4D_SHIFT);
printk("PUD_SHIFT = %d\n", PUD_SHIFT);
printk("PMD_SHIFT = %d\n", PMD_SHIFT);
printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);

printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);
printk("PTRS_PER_P4D = %d\n", PTRS_PER_P4D);
printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);
printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);
printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);
printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);
}

static unsigned long vaddr2paddr(unsigned long vaddr)
{
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
unsigned long paddr = 0;
unsigned long page_addr = 0;
unsigned long page_offset = 0;
pgd = pgd_offset(current->mm, vaddr); // 根据当前虚拟地址和当前进程的mm_struct获取pgd项
printk("pgd_val = 0x%lx, pgd_index = %lu\n", pgd_val(*pgd),pgd_index(vaddr));
if (pgd_none(*pgd)){
printk("not mapped in pgd\n");
return -1;
}

p4d = p4d_offset(pgd, vaddr);
printk("p4d_val = 0x%lx, p4d_index = %lu\n", p4d_val(*p4d),p4d_index(vaddr));
if(p4d_none(*p4d))
{
printk("not mapped in p4d\n");
return -1;
}

pud = pud_offset(p4d, vaddr);
printk("pud_val = 0x%lx, pud_index = %lu\n", pud_val(*pud),pud_index(vaddr));
if (pud_none(*pud)) {
printk("not mapped in pud\n");
return -1;
}

pmd = pmd_offset(pud, vaddr);
printk("pmd_val = 0x%lx, pmd_index = %lu\n", pmd_val(*pmd),pmd_index(vaddr));
if (pmd_none(*pmd)) {
printk("not mapped in pmd\n");
return -1;
}

pte = pte_offset_kernel(pmd, vaddr);
printk("pte_val = 0x%lx, ptd_index = %lu\n", pte_val(*pte),pte_index(vaddr));

if (pte_none(*pte)) {
printk("not mapped in pte\n");
return -1;
}
page_addr = pte_val(*pte) & PAGE_MASK;
page_offset = vaddr & ~PAGE_MASK;
paddr = page_addr | page_offset;
printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);
printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);
return paddr;
}

static int __init v2p_init(void)
{
unsigned long vaddr = 0 ;
printk("vaddr to paddr module is running..\n");
get_pgtable_macro();
printk("\n");
vaddr = __get_free_page(GFP_KERNEL);
if (vaddr == 0) {
printk("__get_free_page failed..\n");
return 0;
}
sprintf((char *)vaddr, "hello world from kernel");
printk("get_page_vaddr=0x%lx\n", vaddr);
vaddr2paddr(vaddr);
ssleep(600);
return 0;
}
static void __exit v2p_exit(void)
{
printk("vaddr to paddr module is leaving..\n");
free_page(vaddr);
}


module_init(v2p_init);
module_exit(v2p_exit);
MODULE_LICENSE("GPL");

100100010 100111001 110000010 101001001 000000000000

0x122 0x139 0x182 0x149

进程管理

打印task_struct信息

通过init_task遍历进程链表实现打印功能

模块源代码 task_struct.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/fdtable.h>
#include <linux/fs_struct.h>
#include <linux/mm_types.h>
#include <linux/types.h>
#include <asm/atomic.h>
#include <linux/init_task.h>

MODULE_LICENSE("GPL");
static int __init print_pcb(void)
{
struct task_struct *task, *p;
struct list_head *pos;
int count = 0;

printk("begin...\n");

task = &init_task;
list_for_each(pos, &task->tasks)
{
p = list_entry(pos, struct task_struct, tasks);
count++;
printk("\n\n");
printk("pid: %d; stack: %p; state: %lx; prio: %d; static_prio: %d; parent's pid: %d; file_count: %d; umask: %d;\n", p->pid,p->stack , p->state, p->prio, p->static_prio, (p->parent)->pid, atomic_read(&(p->files)->count), (p->fs)->umask);
if(p->mm !=NULL)
printk("total_vm: %ld", (p->mm)->total_vm);
}

printk("进程的个数: %d\n", count);
return 0;
}

static void __exit exit_pcb(void)
{
printk("exiting...\n");
}

module_init(print_pcb);
module_exit(exit_pcb);

内存管理


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!