【读书笔记】【linux kernel development】【进程管理】

来源:互联网 发布:mysql sql语句编写 编辑:程序博客网 时间:2024/05/17 00:02

【第三章 进程管理】

 

13.每个进程拥有独立的程序计数器、进程栈和一组进程寄存器。

内核调度的对象是线程而非进程。

Linux系统不区分线程和进程。

 

14.内核把进程的列表存放在任务队列(tasklist)的双向循环链表中。

链表中的每一项都是类型为task_struct,称为进程描述符(process descriptor )的结构。

定义于<liunx/sched.h>中

 

15.Linux通过Slab 分配器分配task_struct 结构,这样能达到对象复用和缓存着色(cache cloring)的目的

每个任务的thread_info 结构在他的内核栈的尾端分配。

 

struct thread_info{struct task_struct*task;struct exec_domain*exec_domain;_u32flag;_u32status;_u32cpu;int preempt_count;mm_segment_taddr_limit;strtct restart_blockrestart_block;void *sysenter_return;intuaccess_err;};


 

 

16.内核通过一个唯一的进程标识值(process identification value)来标识每个进程。

默认类型为short int 。(32768)

可通过修改 /proc/sys/kernel/pid_max 提高标识值的上限。

 

 

17.为何要将task_struct或thread_info 存于内核栈尾端?

是为了避免使用额外的寄存器专门记录,X86的寄存器并不富裕。

 

 

18.进程状态

①TASK_RUNNING 可执行的,正在执行或者在运行队列等待执行。

②TASK_INTERRUPTIBLE 可中断的,

    进程正在睡眠或者被阻塞,等达到某些条件的达成,之后变成运行态,处于此状态的进程会因为接收到信号而提前唤醒并准备投入运行。

③TASK_UNINTERRUPTIBLE  不可中断的。

④_TASK_TRACED 被其他进程跟踪的进程

⑤_TASK_STOPPED  停止,没有投入运行也不能投入运行。通常按剩余接收到SIGSTOP等信号。

 

 

19.设置当前进程状态 <linux/sched.h>

set_task_state( task, state);

set_current_state(state);

 

 

20.当一个程序执行了系统调用或出发了某个异常,它就陷入了内核空间,此时,我们称内核代表进程执行 并处于 进程上下文。

 

21.写时拷贝(copy on write)

内核先不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝,只有在需要写入数据的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。

 

22.copy_process()函数

①调用dup_task_struct()为新进程创建一个内核栈,thread_info结构和task_struct。这些值和当前进程的值相同。

②检查并确保新创建的这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。

③子进程着手于父进程区分开来,进程描述符内的许多成员都要被清零,或设置为初始值。

④子进程的状态被设置为TASK_UNINTERRUPTIBLE 以保证不会投入运行。

⑤copy_process() 调用copy_flags()以更新task_struct的flages成员。 PF_SUPERRIV标志清零,PF_FORKNOEXEC标志设置。

⑥调用alloc_pid()为新进程分配一个有效的pid。

⑦根据传递给clone() 的参数标志,处理资源。

⑧扫尾工作并返回一个指向子进程的指针。

 

23.内核线程与普通的进程间的区别:

在于内核线程没有独立的地址空间(mm指针被置为null),可被调度、抢占。

新的内核线程是通过kthread 内核进程衍生出来的。

<linux/kthread.h>

 

24.进程终结。 一般由do_exit()完成<linux/exit.c>

①将task_struct中的标志成员设置为PF_EXITING.

②调用 del_timer_sync() 删除任一内核定时器。根据返回的结果确保没有定时器在排队,也没有定时器处理程序在执行。

③如果BSD 的进程记账功能是开启的,do_exit()调用 acct_update_integrals() 来输出记账信息。

④然后调用exit_mm()函数释放进程占用的MM_starcut,如果没有别的进程使用他们(亦即,此地址空间没有被共享),就彻底释放他们。

⑤接下来调用 sem_exit()函数,如果进程排队等候ipc信号,它则离开队伍。

⑥调用exit_files() 和 exit_fs(),以分别递减文件描述符、文件系统数据的引用计数。如果其中某个引用计数的数值降为零,那么就代表没有进程在使用相应的资源,此时可以释放。

⑦接着把存放在task_struct 的exit_code 成员中的任务退出代码置为由exit() 提供的退出代码,或者去完成任何其他由内核机制规定的退出动作,退出代码存放在这里供父进程随时检索。

⑧调用 exit_notify() 向父进程发送信号,给子进程重新找养父,养父为线程组中的其他线程或者init进程,并把进程状态(存放在task_struct 结构的exit_state中)设为EXIT_ZOMBIE。

⑨do_exit() 调用 schedule() 切换到新的进程。因为处于EXIT_ZOMBIE状态的进程不会再调度,所以这是进程所执行的最后一段代码。do_exit()用不返回。

 

25.调用 do_exit()之后,尽管线程已经僵死不能再运行,但是系统还保留了它的进程描述符,这样可以让系统有办法在子进程终结后仍能获得它的信息,因此进程终结时所需的清理工作和进程描述符删除被分开执行。

在父进程获得已经终结的子进程的信息后,或者通知内核它并不关注那些信息后,子进程的task_struct结构才被释放。

 

wait4()标准动作:

挂起调用它的进程,知道其中的一个子进程退出,此时函数返回该子进程的PID。

调用该函数时提供的指针会包含子函数退出代码。

 

26.释放进程描述符。 release_task()

①调用 _exit_signal(),该函数调用_unhash_process(),后者又调用detach_pid() 从pidhash上删除该进程,同时也要从任务列表中删除该进程。

②_exit_signal() 释放目前僵死进程所使用的所有剩余资源,并进行最终统计和记录。

③如果这个进程是线程组最后一个进程,并且领头线程已经死掉,那么 release_task() 就要通知僵死的领头进程的父进程。

④release_task() 调用 put_task_struct() 释放进程内核栈和 thread_info 结构所占的页,并释放 task_struct 所占的 slab 高速缓存。

自此,进程描述符和所有进程独享的资源就全部被释放掉了。

 

 

27.孤儿进程造成的进退维谷。

如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲。否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白耗费内存。

解决方法就是给子进程在当前线程组内找一个线程作为父亲,如果不行就让init 做他们的父进程,在 do_exit() 中会调用exit_notify() ,该函数会调用forget_original_parent(),而后者会调用 find_new_reaper()来执行寻父过程。