linux内核堆栈
一:进程的堆栈
内核在创建进程的时候,在创建task_struct的同时会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存 在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内 容是内核栈空间地址,使用内核栈。
内核为每个进程分配task_struct结构体的时候,实际上分配两个连续的物理页面,底部用作task_struct结构体,结构上面的用作堆栈。使用current()宏能够访问当前正在运行的进程描述符。
注意:这个时候task_struct结构是在内核栈里面的,内核栈的实际能用大小大概有7K。
当内核栈为8K时,Thread_info在这块内存的起始地址,内核栈从堆栈末端向下增长。所以此时,要通过thread_info结构体中的task_struct域来获得于thread_info相关联的task。更详细的参考相应的 current宏的实现;
下面是struct thread_info的定义:
50 struct thread_info { 51 unsigned long flags; /* low level flags */ 52 int preempt_count; /* 0 => preemptable, <0 => bug */ 53 mm_segment_t addr_limit; /* address limit */ 54 struct task_struct *task; /* main task structure */ 55 struct exec_domain *exec_domain; /* execution domain */ 56 __u32 cpu; /* cpu */ 57 __u32 cpu_domain; /* cpu domain */ 58 struct cpu_context_save cpu_context; /* cpu context */ 59 __u32 syscall; /* syscall number */ 60 __u8 used_cp[16]; /* thread used copro */ 61 unsigned long tp_value; 62 struct crunch_state crunchstate; 63 union fp_state fpstate __attribute__((aligned(8))); 64 union vfp_state vfpstate; 65 #ifdef CONFIG_ARM_THUMBEE 66 unsigned long thumbee_state; /* ThumbEE Handler Base register */ 67 #endif 68 struct restart_block restart_block; 69 };
通过上面定义我们可以看出在thread_info结构体中有指向task_struct 的指针task;在struct task_struct 结构体中包含着进程的相关信息,下面是相关参数说明;
(1) unsigned short used_math;
是否使用FPU。
(2) char comm[16];
进程正在运行的可执行文件的文件名。
(3) struct rlimit rlim[RLIM_NLIMITS];
结 构rlimit用于资源管理,定义在linux/include/linux/resource.h中,成员共有两项:rlim_cur是资源的当前最大 数目;rlim_max是资源可有的最大数目。在i386环境中,受控资源共有RLIM_NLIMITS项,即10项,定义在 linux/include/asm/resource.h中,见下表:
(4) int errno;
最后一次出错的系统调用的错误号,0表示无错误。系统调用返回时,全程量也拥有该错误号。
(5) long debugreg[8];
保存INTEL CPU调试寄存器的值,在ptrace系统调用中使用。
(6) struct exec_domain *exec_domain;
Linux可以运行由80386平台其它UNIX操作系统生成的符合iBCS2标准的程序。关于此类程序与Linux程序差异的消息就由 exec_domain结构保存。
(7) unsigned long personality;
Linux 可以运行由80386平台其它UNIX操作系统生成的符合iBCS2标准的程序。 Personality进一步描述进程执行的程序属于何种UNIX平台的“个性”信息。通常有PER_Linux、PER_Linux_32BIT、 PER_Linux_EM86、PER_SVR3、PER_SCOSVR3、PER_WYSEV386、PER_ISCR4、PER_BSD、 PER_XENIX和PER_MASK等,参见include/linux/personality.h。
(8) struct linux_binfmt *binfmt;
指向进程所属的全局执行文件格式结构,共有a。out、script、elf和java等四种。结构定义在include/linux /binfmts.h中(core_dump、load_shlib(fd)、load_binary、use_count)。
(9) int exit_code,exit_signal;
引起进程退出的返回代码exit_code,引起错误的信号名exit_signal。
(10) int dumpable:1;
布尔量,表示出错时是否可以进行memory dump。
(11) int did_exec:1;
按POSIX要求设计的布尔量,区分进程是正在执行老程序代码,还是在执行execve装入的新代码。
(12) int tty_old_pgrp;
进程显示终端所在的组标识。
(13) struct tty_struct *tty;
指向进程所在的显示终端的信息。如果进程不需要显示终端,如0号进程,则该指针为空。结构定义在include/linux/tty.h中。
(14) struct wait_queue *wait_chldexit;
在进程结束时,或发出系统调用wait4后,为了等待子进程的结束,而将自己(父进程)睡眠在该队列上。结构定义在include/linux /wait.h中。
13. 进程队列的全局变量
(1) current;
当前正在运行的进程的指针,在SMP中则指向CPU组中正被调度的CPU的当前进程:
#define current(0+current_set[smp_processor_id()])/*sched.h*/
struct task_struct *current_set[NR_CPUS];
(2) struct task_struct init_task;
即0号进程的PCB,是进程的“根”,始终保持初值INIT_TASK。
(3) struct task_struct *task[NR_TASKS];
进 程队列数组,规定系统可同时运行的最大进程数(见kernel/sched.c)。NR_TASKS定义在include/linux/tasks.h 中,值为512。每个进程占一个数组元素(元素的下标不一定就是进程的pid),task[0]必须指向init_task(0号进程)。可以通过 task[]数组遍历所有进程的PCB。但Linux也提供一个宏定义for_each_task()(见 include/linux/sched.h),它通过next_task遍历所有进程的PCB:
#define for_each_task(p) \
for(p=&init_task;(p=p->next_task)!=&init_task;)
(4) unsigned long volatile jiffies;
Linux的基准时间(见kernal/sched.c)。系统初始化时清0,以后每隔10ms由时钟中断服务程序do_timer()增1。
(5) int need_resched;
重新调度标志位(见kernal/sched.c)。当需要Linux调度时置位。在系统调用返回前(或者其它情形下),判断该标志是否置位。置位的话,马上调用schedule进行CPU调度。
(6) unsigned long intr_count;
记 录中断服务程序的嵌套层数(见kernal/softirq.c)。正常运行时,intr_count为0。当处理硬件中断、执行任务队列中的任务或者执 行bottom half队列中的任务时,intr_count非0。这时,内核禁止某些操作,例如不允许重新调度。
以上内容参考:http://www.cnblogs.com/hongzhunzhun/
下面我们要做的是写一个模块,打印当前进程的名字:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/thread_info.h> 4 #include <linux/sched.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("bunfly"); 8 9 10 int bunfly_init() 11 { 12 13 struct thread_info *tmp = NULL; 14 tmp = (struct thread_info *)((unsigned long )&tmp & ~0x1fff);//摸除低13位(8k对齐)获取thread_info首地址 15 struct task_struct *find = tmp->task;//得到对应的task_strcut地址 16 printk("name is: %s\n", find->comm);//获取当前进程名 17 18 return 0; 19 } 20 21 void bunfly_exit() 22 { 23 printk("goodbye bunfly_exit\n"); 24 } 25 26 module_init(bunfly_init); 27 module_exit(bunfly_exit);
在linux内核中,struct_task是以一个双向循环链表的形式存在:
因此,我们可以循环打印所有的进程名,实现简易的ps命令:
下面是具体代码:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/thread_info.h> 4 #include <linux/sched.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("bunfly"); 8 9 int bunfly_init() 10 { 11 struct thread_info *tmp = NULL; 12 struct task_struct *next = NULL; 13 struct task_struct *find = NULL; 14 struct list_head *list = NULL; 15 16 tmp = current_thread_info();//获得thread_info的首地址 17 find = tmp->task;//找到task_struct的首地址 18 19 next = find; 20 do { 21 printk("next is: %s\n", next->comm); 22 list = next->tasks.next; 23 /*通过tasks的地址找到它所在的结构体(task_struct)的首地址,从而得到comm*/ 24 next = container_of(list, struct task_struct, tasks); 25 26 } while (next != find);//循环打印 27 28 return 0; 29 } 30 31 void bunfly_exit() 32 { 33 printk("goodbye bunfly_exit\n"); 34 } 35 36 module_init(bunfly_init); 37 module_exit(bunfly_exit);
在linux内核中要经常用到宏container_of,这个必须掌握:它的原型如下:
#define container_of(ptr, type, member) ({const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})