linux内核设计与实现一书阅读整理 之第三章

chapter 3 进程管理

3.1 进程

  1. 进程就是处于执行期的程序。
  2. 进程就是正在执行的程序代码的实时结果。
  3. 内核调度的对象是线程而并不是进程。
  4. 在现代操做系统中,进程提供两种虚拟机制:linux

    虚拟处理器
          虚拟内存
  5. 进程是处于执行期的程序以及相关的资源的总称。
  6. 进程包括代码段和其余资源。
  7. 几个函数:缓存

    fork():建立新进程
    exec():建立新的地址空间并把新的程序载入其中
    clone():fork实际由clone实现
    exit():退出执行
    wait4():父进程查询子进程是否终结

    wait()、waitpid():程序退出执行后变为僵死状态,调用这两个消灭掉。函数

3.2 进程描述符及任务结构

  • 内核把进程的列表存放在叫作任务队列的双向循环链表中。
  • 链表中的每一项都是类型为task_struct、称为进程描述符的结构。
  • 进程描述符的类型为task_struct,里面包含的数据有:

3.2.1 分配进程描述符

  1. Linux经过slab分配器分配task_struct结构——能达到对象复用缓存着色的目的。
  2. 分配:每一个任务的堆栈尾端(好比,对于向上增加的堆栈来讲,就是在堆栈的栈顶)有结构体threadinfo,它指向了taskstruct结构体
  3. 每一个任务的threadinfo结构在它的内核栈的尾端分配。 结构中task域中存放的是指向该任务实际taskstruct的指针。

3.2.2 进程描述符的存放

  1. 内核经过一个惟一的进程标识值PID来标识每一个进程。
  2. pid类型为pidt,实际上就是一个int类型,最大值默认设置为32768,如若须要,可有系统管理员经过修改/proc/sys/kernel/pidmax上限。
  3. pid存放在各自进程描述符中。
  4. 内核中的大部分处理处理进程的代码都是经过task_struct进行的;所以,须要经过current宏查找到当前正在运行进程的进程描述符
  5. X86系统中,current把栈指针的后13个有效位屏蔽掉,用来计算出threadinfo的偏移(currentthread_info函数)学习

    movl $-8192, %eax
    andl %esp,%eax

3.2.3 进程状态

进程描述符中的state域是用来描述进程当前状态的。共有五种状态,标志以下:操作系统

  • TASK_RUNNING(运行):进程是可执行的,或者正在执行,或者在运行队列中等待执行
  • TASK_INTERRUPTIBLE(可中断):进程正在睡眠/被阻塞
  • TASK_UNINTERRUPTIBLE(不可中断):睡眠/被阻塞进程不被信号唤醒
  • TASK_TRACED:被其余进程跟踪的进程
  • TASK_STOPPED(中止):进程中止执行;进程没有投入运行也不能投入运行。 接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号时,或者调试时收到任何信号,均可以进入这种状态。

状态转换图以下:线程

3.2.4 设置当前进程状态

  • 使用settaskstate(task,state)函数.设计

    • settaskstate(task,state); //将任务task的状态设置为state指针

    • setcurrentstate(state) 和下面等价调试

    • settaskstate(current,state)

3.2.5 进程上下文

  • 可执行代码从一个可执行文件载入到进程的地址空间执行。当一个程序执行了系统调用,内核就会“表明进程执行”并处于进程上下文中
  • 对比:在中断上下文中,系统不表明进程执行——不会有进程去干扰这些中断处理程序

3.2.6 进程家族树

  • 全部的进程都是PID为1的init进程的后代
  • 对于给定的进程,获取链表中下一个进程:code

    - list_entry(task->tasks.prev,struct
    -  task_struct,tasks)

3.3 进程建立

  1. 通常操做系统产生进程的机制:

    1. 在新的地址空间建立进程
    2. 读入可执行文件
    3. 执行
  2. Unix的机制:

    fork()和exec()。
    
     fork():
          经过拷贝当前进程建立一个子进程。
  3. 子进程与父进程的区别仅在于PID,PPID和某些资源和统计量

    exec():
       读取可执行文件并将其载入地址空间开始运行。

3.3.1 写时拷贝

  • Linux的fork()使用写时拷贝推迟甚至免除拷贝。内核在建立新进程的时候并不复制整个地址空间,而是让父进程和子进程共享同一个拷贝;直到子进程/父进程须要写入的时候才进行拷贝
  • 于是,fork的实际开销只是复制父进程的页表以及给子进程建立惟一的进程描述符

3.3.2 fork()

-** Linux经过clone系统调用实现fork** - 建立进程的大概步骤以下:

fork()、vfork()、__clone()都根据各自须要的参数标志调用clone()。
  由clone()去调用do_fork()。
  do_fork()调用copy_process()函数,而后让进程开始运行。
  返回do_fork()函数,若是copy_process()函数成功返回,新建立的子进程被唤醒并让其投入运行。

通常内核会选择子进程首先执行

3.3.3 vfork()

  1. 除了不拷贝父进程的页表项以外,vfork()系统调用和fork()的功能相同。理想状况下不要调用vfork()。

    子进程做为父进程的一个单独的线程在它的地址空间里运行 ,父进程被阻塞,直到子进程退出或执行exec()。子进程不能向地址空间写入。
  2. vfork()系统调用的实现是经过向clone()传递一个特殊标志来进行的。

  3. 调用copyprocess()是,taskstruct的vfor_done成员被设置为NULL。

  4. 执行dofork()时,若是给定特定标志,则vfordone会指向一个特定地址。
  5. 子进程先开始执行后,父进程不是立刻恢复执行,而是一直等待,知道子进程经过vfordone指针向它发送信号。 在调用mmrelease()时,该函数用于进程退出内存地址空间,而且检查vfor_done是否为空,若是不为空,则会向父进程发送信号。
  6. 回到dofork(),父进程醒来并返回。 ## 3.4 线程在linux中实现 ## 在Linux系统中,线程仅仅被视为一个与其余进程共享某些资源的进程。每一个线程都有本身的taskstruct

3.4.1 建立线程

与普通进程相似,只不过在调用clone()的时候须要传递一些参数标志来指明共享的资源

3.4.2 内核线程

与普通进程的区别只在于内核线程没有独立的地址空间。

- 它只能经过其余内核线程建立;内核经过kthread内核进程衍生全部的内核线程
 - 新建立的线程处于不可运行状态,直到wake_up_process()明确地唤醒它

## 3.5 进程终结 ## - 进程终结时,内核必须释放它所占有的资源并告知父进程。

  • 进程终结的缘由:通常是来自自身,发生在调用exit()系统调用时。

    显式的调用
    隐式的从某个程序的主函数返回
  • 大部分依赖于do_exit()来完成。其中有几个重点:

    ……
    给子进程从新找养父(线程组中的其余线程或者init进程)
    调用schedule()切换到新的进程
    ……
  • 这以后,进程不可运行并处于EXITZONBIE退出状态,占用的全部内存就是内核栈、threadinfo结构和task_struct结构。此时进程存在的惟一目的就是向它的父进程提供信息。

3.5.1 删除进程描述符

  • 释放task_struct结构发生在父进程得到已终结的子进程信息而且通知内核不关注后,须要的系统调用是wait4():

    挂起调用它的进程,直到其中的一个子进程退出,此时函数返回该子进程的PID。
  • 释放进程描述符时,须要调用release_task()。

3.5.2 孤儿进程

  • 概述:父进程在进程以前退出,就会遗留下子进程,也就是孤儿进程
  • 其解决方法:
    • 在当前的线程组内给孤儿进程寻找新的父进程;不然直接以init做为其父进程
    • 调用顺序:
    • -  do_exit()-- 
        -    >forget_original_parent()-
        -    >find_new_parent()-
        -    >ptrace_exit_finish()
        -    (这一函数是为被跟踪的进程寻找父进程,由于被跟踪的进程会以调试程序做为临时父亲)

总结

在本章中,我知道了操做系统的核心概念--进程,也跟着书本学习了进程的通常特性的重要性等等,颇有趣。

参考资料

《linux内核设计与实现》原书第三版

相关文章
相关标签/搜索