linux3.5内核中为所有进程(包括内核进程和用户进程)分配8k或4k的内核栈(可以在内核编译的时候选择),在模块代码中分配的栈内存就是在内核栈中,写模块代码如果要在堆中分配内存,则要使用kmalloc或vmalloc来分配,这是后话。
内核栈的栈底存放这thread_info, 通过获取内核栈,以得到thread_info的信息,thread_info里面有一个指向task_struct (进程描述符, 里面放着进程的各种信息)的指针, 通过这个指针, 就可以获取进程的各种信息了。
先编写获取当前进程名的程序,来更好的了解内核栈。
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <asm/thread_info.h> 4 #include <linux/sched.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("BUNFLY"); 8 9 int test_init() 10 { 11 int i = 0; 12 struct thread_info *p = NULL; 13 p = (struct thread_info *)((unsigned long)&i & ~0x1fff); 14 struct task_struct *t = p->task; 15 printk("task name is %s\n", t->comm); 16 17 return 0; 18 } 19 20 void test_exit() 21 { 22 printk("bye bye\n"); 23 } 24 25 module_init(test_init); 26 module_exit(test_exit);
编译成模块,在开发板运行结果如下;
下面来解释下关键代码:
第十一行;在内核栈上定义一个整型变量i
第十二行,定义指向thread_info结构体指针p
第十三行:根据上图所示 ,已知thread_info结构体在内核栈的栈底,整型变量i在栈上,可以获得i的地址,内核栈的大小是8k。求thread_info结构体的地址。方法就是第十三行代码:将i强制转换成unsigned long 型,再将后面13位置零,再强转为thread_info结构体指针。要理解为什么这样并不难。联想下内存的段页式存储,内存地址中前面为基地址,后面是偏移量,内核栈的大小是8k。说明是以8k大小作为偏移量的。后13位置零就是求出基地址,而thread_info结构体是在栈底的,也就是说,基地址就是thread_info结构体的地址。
第十四行定义内存描述符task_struct结构体指针t,并保存thread_info结构体中task的地址。
第十五行输出进程名称。
系统中所有的进程连成一个双向循环链表,现在我们就通过已经找到进程描述符来输出系统中所有的进程名。
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <asm/thread_info.h> 4 #include <linux/sched.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("BUNFLY"); 8 9 int test_init() 10 { 11 //printk("name is %s\n", current->comm); 12 struct thread_info *p = NULL; 13 p = (struct thread_info *)((unsigned long)&p & ~(8192 - 1)); 14 15 struct task_struct *head = p->task; 16 struct task_struct *tmp = head; 17 do{ 18 printk("name is %s\n", tmp->comm); 19 tmp = container_of(tmp->tasks.next, struct task_struct, tasks); 20 }while(tmp != head); 21 22 23 return 0; 24 } 25 26 void test_exit() 27 { 28 printk("bye bye\n"); 29 } 30 31 module_init(test_init); 32 module_exit(test_exit);
container_of宏在内核中应用很广,一定要看懂。