《Linux内核设计与实现》第三章学习笔记

第三章 进程管理

【学习时间:1小时30分 撰写博客时间:2小时】linux

【学习内容:进程描述符、进程的建立及终结、线程的建立】缓存

进程是Unix操做系统抽象概念中最基本的一种。进程管理是全部操做系统的心脏所在。函数

1、进程

1. 进程是处于执行期的程序。除了可执行程序代码,还包括打开的文件、挂起的信号、内核内部数据、一个或者多个执行线程等多种资源学习

  • 线程是在进程活动中的对象;内核调度的对象是线程而不是进程
  • 在Linux系统中,并不区分线程和进程
  • 可能存在两个或者多个进程执行的是同一个程序;甚至N个进程共享打开的文件、地址空间之类的资源

2. 线程:是进程中活动的对象。每一个线程都有一个独立的程序计数器,进程栈和一组进程寄存器。内核调度的对象是线程。spa

3. 在现代操做系统中,进程提供两种虚拟机制:虚拟处理器和虚拟内存。同一进程中的线程之间能够共享虚拟内存,可是每一个都拥有本身的虚拟存储器操作系统

4. 进程的生命周期线程

  • 新建立的进程调用exec()这组函数就能够建立新的地址空间,并把新的程序载入其中;
  • 程序经过exit()函数能够退出执行;进程退出执行以后就会变为僵尸进程,直到父进程调用wait()或waitpid()返回关于终止进程的状态

5. 进程在建立它的时刻开始存活,这一般是调用fork系统的结果。该系统调用经过复制一个现有进程来建立一个全新的进程。fork系统调用从内核返回两次,一次到父进程,另外一次回到新产生的子进程。3d

2、进程描述符及任务结构

进程描述符:进程列表存放在任务队列(task list)这一双向链表中,链表的项是task_struct即进程描述符的结构。该类型定义在<linux/sched.h>中。进程描述符包含的数据能完整地描述一个正在执行的程序:指针

  • 它打开的文件
  • 进程的地址空间
  • 挂起的信号
  • 进程的状态
  • 其余更多信息

2.1 进程描述符的分配

  • 目的:Linux经过slab分配task_ struct结构,以达到对象复用以及和缓存着色的目的,避免资源动态分配和释放带来的资源消耗
  • 如今用slab分配器动态生成task_ struct,因此只须要在栈底(对向下增加的栈来讲)或栈顶(对向上增加的栈来讲)建立一个新的结构struct thread_ info

  • 每一个任务的堆栈尾端(好比,对于向上增加的堆栈来讲,就是在堆栈的栈顶)有结构体thread_ info,它指向了task_ struct结构体

2.2 进程描述符的存放

1. 内核中的大部分处理处理进程的代码都是经过task_ struct进行的;所以,须要经过current宏查找到当前正在运行进程的进程描述符code

2. X86系统中,current把栈指针的后13个有效位屏蔽掉,用来计算出thread_ info的偏移(经过current_ thread_ info函数)

movl $-8192, %eax
andl %esp,%eax3.进程状态

 

2.3 进程状态

进程在任什么时候刻,都一定处于五种状态中的一种

  • TASK_RUNNING 运行
  • TASK_INTERRUPT 可中断
  • TASK_UNINTERRUPT 不可中断
  • TASK_TRACED 被其余进程跟踪的进程
  • TASK_STOPPED 进程中止运行

  • TASK_RUNNING:多是正在运行,也可能表示可执行
  • TASK_ INTERRUPT/TASK_UNINTERRUPT:都表示正在阻塞;然然后者表示的状态收到信号以后也不会被唤醒

2.4 设置进程当前状态

调用set_ task_ state(task,state)函数将进程设置为指定状态

2.5 进程上下文

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

2.6 进程家族树

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

    list_ entry(task->tasks.prev,struct task_struct,tasks)

3、进程建立

Unix系统的进程建立方式

  • fork()经过拷贝当前进程建立一个子进程
  • exec()负责读取可执行文件并将其载入地址空间开始运行

3.1 写时拷贝

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

3.2 fork()

  1. Linux经过clone系统调用实现fork
  2. 由clone去调用do_fork()
  3. 定义在<kernel/fork.c>中的do_ fork()完成建立中的大部分工做,它调用copy_process函数,而后让进程开始运行

  最后copy_process返回的就是指向子进程的指针

3.3 vfork()

  除了不拷贝父进程的页表项外,vfork()系统调用和fork()功能相同。子进程做为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec()。

  vfork()系统调用的实现是经过向clone()系统调用传递一个特殊标志来进行的:

4、线程在Linux中的实现

  线程机制提供了在同一程序内共享内存地址空间运行的一组线程。在Linux系统中,线程仅仅被视为一个与其余进程共享某些资源的进程。每一个线程都有本身的task_struct。

4.1 建立线程

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

2. 传递给clone()的参数标志决定了新建立进程的行为方式和父子进程之间共享的资源种类

4.2 内核线程

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

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

5、进程终结

终结进程大部分依赖于do_exit()来完成:

5.1 删除进程描述符

  1. 该任务是和清理工做分开进行的,由于这样在进程终结以后系统仍然能够得到它的信息
  2. 经过release_task()实现进程描述符的删除
  3. 至此,全部资源都被释放了

5.2 解决孤儿进程

1. 孤儿进程:父进程在进程以前退出,就会遗留下子进程,也就是孤儿进程

2. 解决方法:在当前的线程组内给孤儿进程寻找新的父进程;不然直接以init做为其父进程

  • 调用顺序:do_ exit()-->forget_ original_ parent()-->find_ new_ parent()-->ptrace_ exit_ finish()

一旦系统为进程成功地找到和设置了新的父进程,就不会再有出现驻留僵死进程的危险了。init进程会例行调用wait()来检查其子进程,清除全部与其相关的僵死进程。

总结

  经过本章的学习,我深刻理解了操做系统的核心概念——进程,以及进程与线程之间的关系。同时掌握了在Linux中使用task_ struct和thread_info存放和表示进程,经过fork()建立进程的具体过程。进程是一个很是基础的抽象概念,对有关进程调度的理解有相当重要的做用。

相关文章
相关标签/搜索