Skip to content

Commit e5aa2e3

Browse files
authored
Linux内核堆栈浅谈
1 parent cb2aaf2 commit e5aa2e3

File tree

1 file changed

+286
-0
lines changed

1 file changed

+286
-0
lines changed

文章/Linux内核堆栈浅谈.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
内核为每个进程分配一个task_struct结构时,实际上分配两个连续的物理页面(8192字节),如图所示。底部用作task_struct结构(大小约为1K字节),结构的上面用作内核堆栈(大小约为7K字节)。访问进程自身的task_struct结构,使用宏操作current, 在2.4中定义如下:
2+
3+
![img](https://pic2.zhimg.com/80/v2-175f0020378c22ce70736eb4889c32e5_720w.webp)
4+
5+
根据内核的配置,THREAD_SIZE既可以是4K字节(1个页面)也可以是8K字节(2个页面)。thread_info是52个字节长。
6+
下图是当设为8KB时候的内核堆栈:Thread_info在这个内存区的开始处,内核堆栈从末端向下增长。进程描述符不是在这个内存区中,而分别通过task与thread_info指针使thread_info与进程描述符互联。所以获得当前进程描述符的current定义如下:
7+
8+
![img](https://pic2.zhimg.com/80/v2-883080a042dcaaba752d1603f81f3ab9_720w.webp)
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+
![img](https://pic3.zhimg.com/80/v2-9383324bde77195778798d571aac74a2_720w.webp)
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+
![img](https://pic4.zhimg.com/80/v2-9c3883b480a5eac7f33fa7a91a36feeb_720w.webp)
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

Comments
 (0)