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) );})

 

posted @ 2015-09-12 10:18  zhangwju  阅读(5666)  评论(0编辑  收藏  举报