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

chapter 3 进程管理

3.1 进程

  1. 进程就是处于执行期的程序。
  2. 进程就是正在执行的程序代码的实时结果。
  3. 内核调度的对象是线程而并非进程。
  4. 在现代操作系统中,进程提供两种虚拟机制:
          虚拟处理器
          虚拟内存
    
  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进程的后代
  • 对于给定的进程,获取链表中下一个进程:
    - 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内核设计与实现》原书第三版

时间: 2024-07-29 22:27:01

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

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

第一章:Linux内核简介 一.Unix和linux Unix是一个强大.健壮和稳定的操作系统. 1.Unix内核特点 十分简洁:仅提供几百个系统调用并且有明确的目的: 在Unix中,大部分东西都被(或者正致力于)被当做文件对待: Unix内核即相关系统工具软件都是用C语言编写的,这使得系统有着强大的可移植性: Unix进程创建非常迅速,目标在于一次执行保质保量地完成一个任务 2.Linux与Unix异同 Linux是基于Unix的类系统,比如它也实现了Unix的API: 但它不同于Unix,没

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

CHAPTER 18 调试 18.1 准备开始 需要的是准备是: - 一个bug - 一个藏匿bug的内核版本 - 相关内核代码的知识和运气 重点: 想要成功的进行调试,就取决于是否能让这些错误重现.如若不能,消灭bug就只能通过抽象出问题,再从代码中寻找蛛丝马迹来进行了. 18.2 内核中的bug bug出现时可能的症状: 错误代码.(如没把正确的值存放在恰当的位置) 同步时发生的错误.(如共享变量锁定不当) 错误的管理硬件.(如给错误的控制寄存器发送错误的指令) ...... 内核bug发作

Linux内核设计与实现高清版pdf免费下载

下载地址:网盘下载 备用地址:网盘下载 内容简介编辑<Linux内核设计与实现(原书第3版)>基于Linux 2.6.34内核详细介绍了Linux内核系统,覆盖了从核心内核系统的应用到内核设计与实现等各方面的内容.<Linux内核设计与实现(原书第3版)>主要内容包括:进程管理.进程调度.时间管理和定时器.系统调用接口.内存寻址.内存管理和页缓存.VFS.内核同步以及调试技术等.同时<Linux内核设计与实现(原书第3版)>也涵盖了Linux 2.6内核中颇具特色的内容

Linux内核设计与实现下载 &#143719;

下载地址: http://www.gqylpy.com/di/15 <Linux内核设计与实现>PDF高清完整版-下载 内容简介 编辑 <Linux内核设计与实现(原书第3版)>基于Linux 2.6.34内核详细介绍了Linux内核系统,覆盖了从核心内核系统的应用到内核设计与实现等各方面的内容.<Linux内核设计与实现(原书第3版)>主要内容包括:进程管理.进程调度.时间管理和定时器.系统调用接口.内存寻址.内存管理和页缓存.VFS.内核同步以及调试技术等.同时&l

读书笔记2013-2 Linux内核设计与实现A

<Linux内核设计与实现> 简介 这本书不是想Linux源码剖析那样一行行分析Linux源代码的书,而是从Linux历史,Linux哲学,Linux设计原理和原则,计算机硬件相关知识,编译安装内核实战等多方面多角度讲述和Linux相关的方方面面.从中学到的更多的不单内核的源代码,而是Linux的哲学.建议所有从事Linux相关工作的猿都要读一下,读完之后,很多东西都变得容易理解,知其然,知其所以然.如果没有读过<深入理解计算机>类似的图书,建议和<深入理解计算机>一起

《LINUX内核设计的艺术》第一章从开机家电到执行main函数之前的过程 学习笔记之一

从开机加电到实行main函数之前的过程 分为三步,目的是实现从启动盘加载操作系统程序,完成实现main函数的准备工作 启动BLOS,准备是模式下的中断向量表和中断服务程序 从启动盘加载操作系统程序到内存.加载操作系统程序就是靠第一步实现的 为实现32位的main函数做过度工作 1.1启动blos,准备实模式下的中断向量表和中断服务程序 由blos来加载软件操作系统的任务 1.1.1         BLOS的启动原理 0XFFFF0 由硬件来启动,CPU硬件设计逻辑设计为加电瞬间就强行将CS的值

《Linux内核设计与实现》读书笔记(十九)- 可移植性

摘自http://www.cnblogs.com/wang_yb/p/3512095.html <Linux内核设计与实现>读书笔记(十九)- 可移植性 linux内核的移植性非常好, 目前的内核也支持非常多的体系结构(有20多个). 但是刚开始时, linux也只支持 intel i386 架构, 从 v1.2版开始支持 Digital Alpha, Intel x86, MIPS和SPARC(虽然支持的还不是很完善). 从 v2.0版本开始加入了对 Motorala 68K和PowerPC

《Linux内核设计与实现》笔记——内核同步简介

相关概念 竞争条件 多个执行线程(进程/线程/中断处理程序)并发(并行)访问共享资源,因为执行顺序不一样造成结果不一样的情况,称为竞争条件(race condition) 举例说明 #include<thread> using namespace std; int i = 0; void thread1(){ //for(int x=0;x<100000;x++) i++; } void thread2(){ //for(int x=0;x<100000;x++) i++; } i

【读书笔记】《Linux内核设计与实现》内核同步介绍&内核同步方法

简要做个笔记,以备忘. 需同步的原因是,我们并发访问了共享资源.我们将访问或操作共享资源的代码段称"临界区",如果两个执行线程处于同一临界区中同时执行,称"竞争条件".这里术语执行线程指任何正在执行的代码实例,如一个在内核执行的进程.一个中断处理程序或一个内核线程. 举个简单例子,i++操作.该操作可以转换为下面的机器指令序列: 1.得到当前变量i的值,并保存到一个寄存器. 2.将寄存器的值加1. 3.将i的新值写回到内存中. 当两个线程同时进入这个临界区,若i初值