深入理解linux内核读书笔记2
第三章 进程
进程、轻量级进程和线程
进程定义:进程是程序执行是的一个实例
从内核观点看,进程的目的就是担任分配系统资源(CPU时间、内存等)的实体
在linux源代码中,常将进程成为任务(task)或线程(thread)
Linux早期版本中,父子进程间只能通过数据拷贝来实现进程数据保护。
Linux现在使用轻量级进程(LWP/LightWeight Process)对多线程应用程序提供更好的支持。两个轻量级进程可以共享一些资源,诸如地址空间、打开的文件等。
只要轻重一个修改共享资源,另一个马上能看到这种修改。
当然,当两个线程访问共享资源是就必须同步它们自己。
进程描述符都是task struct类型结构的
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
struct thread_info *thread_info;
……
};
此处仅讨论进程的状态和进程的父子之间关系
进程状态:
TASK_RUNNING//Executing or waiting to be executed
TASK_INTERRUPTIBLE//Hardware interruput or delivering a signal .. might wake up the process
TASK_UN INTERRUPTIBLE//Seldom used , Opening a device file might use it
TASK_STOPPED//receving a SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU and enter this state
TASK_TRACED
EXIT_ZOMBIE//Procees execution is terminated, but the parent process has not yet issued a wait4() or waitpid() system call to return information about the dead process.
EXIT_DEAD//The final state: the process is being remove by the system because the parent process has just issued a wait4() or waitpid() system call for it.
标志一个进程
遵照POSIX标准,linux引入线程组的表示。一个线程组中的所有县城使用和改线程组的领头县城(thread group leader)相同的PID,也就是该组中第一个轻量级进程的PID,它被存入进程描述符的tgid字段中。故getpid()系统调用返回的是当前进程的tgid()而非pid的值。
对每个进程来说,linux都把两个不同的数据结构紧凑地存放在一个单朵为进程分配的存储区域内:一个是内核态的进程堆栈,另一个是紧挨进程描述符的小数据结构thread_info,叫做线程描述符。这块存储区大小通常为8192个字节(4KB页框,共2页)。
普通数据段和堆栈段的存取方式不同,一个是逐个递增,一个是逐个递减,所以可以形成下图的构造。而且,通过current_thread_info()->task的宏,可以轻易找到thread_info中task字段的地址(进程描述符地址)。
实际汇编指令如下:
movl $0xfffe0000,%ecx
andl %esp,%ecx
movl %ecx,p
无论是当前的sp地址是0x015f a878还是0x015 bfff,与掩码0xffff e000相与之后,都将得到0x015f a000
双向链表
内核进程跟踪系统采用了两种双向链表
1)Prev/Next 指向list_name变量
Prev<------------------------\
Next<--->Prev |
Next<--->Prev |
Next<-----/
2)Pprev 指向前一元素的Next字段
Next 指向下一元素
主要用于散列表
NULL<---Pprev|Next<--->Pprev|Next<--->Pprev|Next--->NULL
进程链表的实际实现不讨论了,讨论一个在第一种进程链表状态下的一个很有用的函数for_each_process(p),它的功能是扫描整个进程链表。
#define for_each_process(p) \
for (p = &init_task ; (p = next_task(p)) != &init_task ; )
从下一个task开始,回到init_task的时候结束。接着寻根问底一番
#define next_task(p) list_entry((p)->tasks.next, struct task_struct, tasks)
#define prev_task(p) list_entry((p)->tasks.prev, struct task_struct, tasks)
list_entry(p,t,m)在书中的意思是,返回类型为t的数据结构的地址,其中类型t中含有list_head字段,而list_head字段中含有名字m和地址p,接着来看些list_entry原型
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
接下来重点分析offsetof和container_of函数:
#define offsetof(TYPE, MEMBER) \
((size_t) & ((TYPE *)0)->MEMBER )
1)(TYPE *)0 将0的类型定义成type型指针
2)((TYPE *)0)->MENBER取0地址处结构的成员MENBER
3)& 取地址
4)(size_t) 强制转换类型,转换后变成数值表示
typedef __kernel_size_t size_t;
typedef unsigned long __kernel_size_t;
---------------------------------------------------分割线
const typeof( ((type *)0)->member ) *__mptr = (ptr);
1)((type *)0)->member ) 同上
2)..*__mptr = (ptr)定义了一个m(ember)ptr的指针变量,ptr其实是指向member的指针
3)(char *) 将指针变量强制转换成字符型指针
4)(type *)(.. __mptr - offset) member的地址减去member的偏移量,实际上得到的是task_struct结构体的首地址,经过type强制转换后,得到的是指向这个首地址的指针
5)({}) 返回最后一个执行语句的结果,即首地址指针
分析到这里先来总结一下offsetof/typeof及这两句程序段的巧妙之处,将(type *)0使用从而求出首项的偏移地址,很好地使用了汇编的思想,而后面的__mptr – offset的时候也体现了汇编寻址的思想。
现在再回到前面来看一下list_entry的调用实现了什么功能。
ptr为(p)->tasks.next
type为struct task_struct
member为tasks
这样一看,list_entry实际上是返回了下一个task_struct的首地址,而在循环判断中
for( p=&init_task; (p=list_entry())!=&init_task; )
以为着从p这个进程开始,直到实现了一次遍历才停止循环。
值得再次注意的是,这里所使用的链表类型必须为第一种链表,这种链表才能循环至开头。
所以使用下面的函数可以打印出每一个任务的名称和pid
for_each_process(task) {
/* 它打印出每一个任务的名称和PID*/
printk("%s[%d]\n", task->comm, task->pid);
}
Pidhash表(第二种类型的表)和等待队列不讨论了。
进程切换的宏switch_to……
哎哟喂、这不能画图要怎么说呢、给个链接吧
http://wenku.baidu.com/view/f9a17542b307e87101f6968d.html###