|
| 1 | +内核为每个进程分配一个task_struct结构时,实际上分配两个连续的物理页面(8192字节),如图所示。底部用作task_struct结构(大小约为1K字节),结构的上面用作内核堆栈(大小约为7K字节)。访问进程自身的task_struct结构,使用宏操作current, 在2.4中定义如下: |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +根据内核的配置,THREAD_SIZE既可以是4K字节(1个页面)也可以是8K字节(2个页面)。thread_info是52个字节长。 |
| 6 | +下图是当设为8KB时候的内核堆栈:Thread_info在这个内存区的开始处,内核堆栈从末端向下增长。进程描述符不是在这个内存区中,而分别通过task与thread_info指针使thread_info与进程描述符互联。所以获得当前进程描述符的current定义如下: |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +下面是thread_info结构体的定义: |
| 11 | + |
| 12 | +```text |
| 13 | +struct thread_info { |
| 14 | + struct task_struct *task; /* main task structure */ |
| 15 | + struct exec_domain *exec_domain; /* execution domain */ |
| 16 | + __u32 flags; /* low level flags */ |
| 17 | + __u32 status; /* thread synchronous flags */ |
| 18 | + __u32 cpu; /* current CPU */ |
| 19 | + int preempt_count; /* 0 => preemptable, <0 => BUG */ |
| 20 | + mm_segment_t addr_limit; |
| 21 | + struct restart_block restart_block; |
| 22 | + void __user *sysenter_return; |
| 23 | + #ifdef CONFIG_X86_32 |
| 24 | + unsigned long previous_esp; /* ESP of the previous stack in |
| 25 | + case of nested (IRQ) stacks |
| 26 | + */ |
| 27 | + __u8 supervisor_stack[0]; |
| 28 | + #endif |
| 29 | + unsigned int sig_on_uaccess_error:1; |
| 30 | + unsigned int uaccess_err:1; /* uaccess failed */ |
| 31 | + }; |
| 32 | +``` |
| 33 | + |
| 34 | +可以看到在thread_info中个task_struct结构体,里面包含的是进程描述符,其他的参数如下:(可以略过) |
| 35 | + |
| 36 | +(1) unsigned short used_math; |
| 37 | + |
| 38 | +是否使用FPU。 |
| 39 | + |
| 40 | +(2) char comm[16]; |
| 41 | + |
| 42 | +进程正在运行的可执行文件的文件名。 |
| 43 | + |
| 44 | +(3) struct rlimit rlim[RLIM_NLIMITS]; |
| 45 | + |
| 46 | +结 构rlimit用于资源管理,定义在linux/include/linux/resource.h中,成员共有两项:rlim_cur是资源的当前最大 数目;rlim_max是资源可有的最大数目。在i386环境中,受控资源共有RLIM_NLIMITS项,即10项,定义在 linux/include/asm/resource.h中,见下表: |
| 47 | + |
| 48 | +(4) int errno; |
| 49 | + |
| 50 | +最后一次出错的系统调用的错误号,0表示无错误。系统调用返回时,全程量也拥有该错误号。 |
| 51 | + |
| 52 | +(5) long debugreg[8]; |
| 53 | + |
| 54 | +保存INTEL CPU调试寄存器的值,在ptrace系统调用中使用。 |
| 55 | + |
| 56 | +(6) struct exec_domain *exec_domain; |
| 57 | + |
| 58 | +Linux可以运行由80386平台其它UNIX操作系统生成的符合iBCS2标准的程序。关于此类程序与Linux程序差异的消息就由 exec_domain结构保存。 |
| 59 | + |
| 60 | +(7) unsigned long personality; |
| 61 | + |
| 62 | +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。 |
| 63 | + |
| 64 | +(8) struct linux_binfmt *binfmt; |
| 65 | + |
| 66 | +指向进程所属的全局执行文件格式结构,共有a。out、script、elf和java等四种。结构定义在include/linux /binfmts.h中(core_dump、load_shlib(fd)、load_binary、use_count)。 |
| 67 | + |
| 68 | +(9) int exit_code,exit_signal; |
| 69 | + |
| 70 | +引起进程退出的返回代码exit_code,引起错误的信号名exit_signal。 |
| 71 | + |
| 72 | +(10) int dumpable:1; |
| 73 | + |
| 74 | +布尔量,表示出错时是否可以进行memory dump。 |
| 75 | + |
| 76 | +(11) int did_exec:1; |
| 77 | + |
| 78 | +按POSIX要求设计的布尔量,区分进程是正在执行老程序代码,还是在执行execve装入的新代码。 |
| 79 | + |
| 80 | +(12) int tty_old_pgrp; |
| 81 | + |
| 82 | +进程显示终端所在的组标识。 |
| 83 | + |
| 84 | +(13) struct tty_struct *tty; |
| 85 | + |
| 86 | +指向进程所在的显示终端的信息。如果进程不需要显示终端,如0号进程,则该指针为空。结构定义在include/linux/tty.h中。 |
| 87 | + |
| 88 | +(14) struct wait_queue *wait_chldexit; |
| 89 | + |
| 90 | +在进程结束时,或发出系统调用wait4后,为了等待子进程的结束,而将自己(父进程)睡眠在该队列上。结构定义在include/linux /wait.h中。 |
| 91 | + |
| 92 | +**13. 进程队列的全局变量** |
| 93 | + |
| 94 | +(1) current; |
| 95 | + |
| 96 | +当前正在运行的进程的指针,在SMP中则指向CPU组中正被调度的CPU的当前进程: |
| 97 | + |
| 98 | +\#define current(0+current_set[smp_processor_id()])/*sched.h*/ |
| 99 | + |
| 100 | +struct task_struct *current_set[NR_CPUS]; |
| 101 | + |
| 102 | +(2) struct task_struct init_task; |
| 103 | + |
| 104 | +即0号进程的PCB,是进程的“根”,始终保持初值INIT_TASK。 |
| 105 | + |
| 106 | +(3) struct task_struct *task[NR_TASKS]; |
| 107 | + |
| 108 | +进程队列数组,规定系统可同时运行的最大进程数(见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: |
| 109 | + |
| 110 | +\#define for_each_task(p) \ |
| 111 | + |
| 112 | +for(p=&init_task;(p=p->next_task)!=&init_task;) |
| 113 | + |
| 114 | +(4) unsigned long volatile jiffies; |
| 115 | + |
| 116 | +Linux的基准时间(见kernal/sched.c)。系统初始化时清0,以后每隔10ms由时钟中断服务程序do_timer()增1。 |
| 117 | + |
| 118 | +(5) int need_resched; |
| 119 | + |
| 120 | +重新调度标志位(见kernal/sched.c)。当需要Linux调度时置位。在系统调用返回前(或者其它情形下),判断该标志是否置位。置位的话,马上调用schedule进行CPU调度。 |
| 121 | + |
| 122 | +(6) unsigned long intr_count; |
| 123 | + |
| 124 | +记 录中断服务程序的嵌套层数(见kernal/softirq.c)。正常运行时,intr_count为0。当处理硬件中断、执行任务队列中的任务或者执 行bottom half队列中的任务时,intr_count非0。这时,内核禁止某些操作,例如不允许重新调度。 |
| 125 | + |
| 126 | +接下来写一个模块,打印当前的进程名字: |
| 127 | + |
| 128 | +```text |
| 129 | +#include <linux/init.h> |
| 130 | +#include <linux/thread_info.h> |
| 131 | +#include <linux/module.h> |
| 132 | +#include <linux/sched.h> |
| 133 | +
|
| 134 | +MODULE_LICENSE("GPL"); |
| 135 | +MODULE_AUTHOR("binary_tree"); |
| 136 | +
|
| 137 | +int test_init() |
| 138 | +{ |
| 139 | + printk("hello binary_tree!\n"); |
| 140 | +
|
| 141 | + int i=0; |
| 142 | + struct thread_info * info; |
| 143 | + struct task_struct * t; |
| 144 | +
|
| 145 | + unsigned long addr =(unsigned long)&i;// |
| 146 | + unsigned long base = addr & ~ 0x1fff;//屏蔽低13位 8K |
| 147 | + info = (struct thread_info *)base;// |
| 148 | + t= info -> task; |
| 149 | + printk("it is name is %s\n",t-> comm);//打印出进程名字 |
| 150 | + return 0; |
| 151 | +} |
| 152 | +
|
| 153 | +void test_exit() |
| 154 | +{ |
| 155 | + printk("bye! bye ! binary_tree! \n"); |
| 156 | +} |
| 157 | +
|
| 158 | +
|
| 159 | +module_init(test_init); |
| 160 | +module_exit(test_exit); |
| 161 | +``` |
| 162 | + |
| 163 | +在栈中,struct_task的存在方式如下: |
| 164 | + |
| 165 | + |
| 166 | + |
| 167 | +可以看到在struct_task中有一个staks的结构体,就是一个循环双向链表,因此,我们可以模拟出一个PS命令: |
| 168 | + |
| 169 | +(今天布置的习题) |
| 170 | + |
| 171 | +```text |
| 172 | +#include <linux/init.h> |
| 173 | +#include <linux/thread_info.h> |
| 174 | +#include <linux/module.h> |
| 175 | +#include <linux/sched.h> |
| 176 | +
|
| 177 | +MODULE_LICENSE("GPL"); |
| 178 | +MODULE_AUTHOR("bunfly"); |
| 179 | +
|
| 180 | +int test_init() |
| 181 | +{ |
| 182 | + int i = 0; |
| 183 | + struct task_struct *t; |
| 184 | + struct thread_info *info; |
| 185 | +
|
| 186 | + unsigned long addr = (unsigned long)&i; |
| 187 | + unsigned long base = addr & ~0x1fff; |
| 188 | + info = (struct thread_info *)base; |
| 189 | + t = info->task; |
| 190 | +
|
| 191 | + struct task_struct* flag = t; |
| 192 | + struct task_struct *next = container_of(t->tasks.next, struct task_struct, tasks); |
| 193 | + //首地址 子类的类型, 父类 |
| 194 | + //获得t下一个进程符next; |
| 195 | + printk("now comm is %s\n",t->comm); |
| 196 | +
|
| 197 | + struct task_struct *nnext = container_of(next->tasks.next, struct task_struct, tasks); |
| 198 | + //获得next下一个进程nnext |
| 199 | + while(nnext != flag) |
| 200 | + { |
| 201 | + next=nnext; |
| 202 | + nnext=container_of(next->tasks.next, struct task_struct, tasks); |
| 203 | + printk("next comm is %s\n",next->comm); |
| 204 | + } |
| 205 | + //依次循环打印下一个进程 |
| 206 | + printk("nnext comm is %s\n",nnext->comm); |
| 207 | + return 0; |
| 208 | +} |
| 209 | +
|
| 210 | +void test_exit() |
| 211 | +{ |
| 212 | + printk("exit\n"); |
| 213 | +} |
| 214 | +
|
| 215 | +module_init(test_init); |
| 216 | +module_exit(test_exit); |
| 217 | +``` |
| 218 | + |
| 219 | +在代码中,用到一个函数:container_of(ptr,type,mem),其中三个参数可分别描述为:ptr, type,mem,分别代表: |
| 220 | + |
| 221 | +**ptr**:父类在子类对象中的首地址; |
| 222 | + |
| 223 | +**type**:子类的类型: |
| 224 | + |
| 225 | +**mem**:父类的实体(成员): |
| 226 | + |
| 227 | + |
| 228 | + |
| 229 | +其具体的功能就是求下面是对container_of函数的具体实现:(重点讲解的) |
| 230 | + |
| 231 | +```text |
| 232 | +1 #include<stdio.h> |
| 233 | + 2 |
| 234 | + 3 #define container_of(ptr,type,mem) (type*)((unsigned long )(ptr)-(unsigned long)(&((type*)0)->mem)); |
| 235 | + 4 struct person |
| 236 | + 5 { |
| 237 | + 6 int age; |
| 238 | + 7 struct person* next; |
| 239 | + 8 }; |
| 240 | + 9 |
| 241 | + 10 struct man |
| 242 | + 11 { |
| 243 | + 12 int len; |
| 244 | + 13 int size; |
| 245 | + 14 char name; |
| 246 | + 15 struct person p; |
| 247 | + 16 }; |
| 248 | + 17 |
| 249 | + 18 int main() |
| 250 | + 19 { |
| 251 | + 20 struct man haha; |
| 252 | + 21 |
| 253 | + 22 haha.len=100; |
| 254 | + 23 haha.p.age=20; |
| 255 | + 24 struct man* head = &haha.p;//已知父类在子类中的首地址 //3 |
| 256 | + 25 // struct man* tmp=malloc(1); |
| 257 | + 26 // int size = (unsigned long)(&tmp->p)-(unsigned long)(tmp); |
| 258 | + 27 //2 |
| 259 | + 28 // struct man* tmp=0; |
| 260 | + 29 // int size = (unsigned long)(&tmp->p); |
| 261 | + 30 // struct man* m=(struct man*)( (unsigned long )(head)-size ); |
| 262 | + 31 //1 |
| 263 | + 32 //struct man* tmp=0; |
| 264 | + 33 //int size = (unsigned long)(&tmp->p); |
| 265 | + 34 //struct man* m=(struct man*)( (unsigned long )(head)-size ); |
| 266 | + 35 |
| 267 | + 36 // struct man* m=(struct man*)((unsigned long )(head)-(unsigned long)(&((struct man*)0)->p)); |
| 268 | + 37 struct man* m = container_of(head,struct man,p); |
| 269 | + 38 |
| 270 | + 39 // printf("head addr is %p\n",&head); |
| 271 | + 40 // printf("m addr is %p\n",&m); |
| 272 | + 41 // printf("head of len is %d\n",head->len); |
| 273 | + 42 // printf("head of age is %d\n",(head->p.age) ); |
| 274 | + 43 |
| 275 | + 44 printf("m of len is %d\n",m->len); |
| 276 | + 45 printf("m of age is %d\n",m->p.age); |
| 277 | + 46 |
| 278 | + 47 } |
| 279 | +~ |
| 280 | +``` |
| 281 | + |
| 282 | +--- |
| 283 | + |
| 284 | +版权声明:本文为知乎博主「[极致Linux内核](https://www.zhihu.com/people/linuxwang-xian-sheng)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 |
| 285 | + |
| 286 | +原文链接:https://zhuanlan.zhihu.com/p/549156208 |
0 commit comments