第三章 进程管理

3.1 进程

进程就是处于执行期的程序

进程就是正在执行的程序代码的实时结果

线程:在进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。

内核调度的对象是线程,不是进程,对于Linux而言,线程只是一种特殊的进程。

进程提供两种虚拟机制:虚拟处理器和虚拟内存

虚拟处理器给进程一种假象,让这些进程觉得自己在独享处理器。

虚拟内存让进程在分配和管理内存时觉得自己拥有整个系统的所有内存资源。

在线程之间可以共享虚拟内存,但每个都拥有各自的虚拟处理器

进程是处于执行器的程序以及相关资源的总称

该系统调用通过复制一个现有进程来创建一个全新的进程

Fork()系统调用返回两次:一次回到父进程,一次回到新产生的子进程

通过exec()这组函数可以创建新的地址空间,把新的程序载入其中

通过exit()退出执行,会终结进程并将其占用的资源放掉。

父进程通过wait4系统调用调查子进程是否终结,使进程有了等待特定进程执行完毕的能力

进程退出执行后被设置为僵死状态,直到它的父进程调用wait()或waitpid()

3.2 进程描述符及任务结构

内核把进程的列表存放在叫做任务队列的双向循环链表中,链表中的每一项都是类型为task_struct、称为进程描述符的结构,定义在<linux/sched.h>文件中

进程描述符中包含的数据能完整地描述一个正在执行的程序

3.2.1 分配进程描述符

Linux通过slab分配器分配task_struct结构

目的:达到对象复用和缓和着色

在栈底或栈顶创建一个新的结构struct_thread_info,在<asm/thread_info.h>下定义

每个任务的thread_info结构在它的内核栈的尾端分配,结构中task域中存放的是指向该任务实际task_struct的指针

3.2.2 进程描述符的存放

内核通过一个唯一的进程标志值或PID来标识每个进程

PID是一个数,表示为pid_t隐含类型,实际上是一个int类型,最大值默认为32768

内核把每一个进程的PID存放在它们各自的进程描述符中

最大值:系统允许同时存在的进程的最大数目,通过修改/proc/sys/kernel/pid_max来提高上限

访问任务通常需要获得指向其task_struct的指针,内核中大部分处理进程的代码都是直接通过task_struct进行的

通过current宏查找到当前正在运行的进程的进程描述符

Current把栈指针的后13个有效位屏蔽掉,用来计算thread_info的偏移,通过current_thread_info()函数来完成

Current再从thread_info的task域中提取并返回task_struct的地址

3.2.3 进程状态

系统中的每个进程都必然处于五个状态的一种:运行、可中断、不可中断、被其他进程跟踪的进程、停止

3.2.4 设置当前进程状态

使用set_task_state(task,state)函数,该函数将指定的进程设置为指定的状态

3.2.5 进程上下文

可执行程序代码:从一个可执行文件载入到进程的地址空间执行

当一个进程执行了系统调用或触发了某个异常,就陷入内核空间,我们称其“代表进程执行”并处于进程上下文中,在此上下文current宏是有效的

系统调用和异常处理程序是对内核明确定义的接口,进程只有通过这些接口才能陷入内核执行——对内核的所有访问都必须通过接口

3.2.6 进程家族树

所有的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程,该进程读取系统的初始化脚本,完成系统调用的整个过程

每个进程必须有一个父进程,每个进程都可以拥有零个或多个子进程,拥有同一个父进程的所有进程被称为兄弟。进程间的关系存放在进程描述符中,每个task_struct都包含一个指向其父进程task_struct、叫做parent的指针,还包含一个称为children的子进程链表

Init进程的进程描述符是作为init_task静态分配的

任务队列:双向循环链表

获取链表下一个进程:next_task(task)

获取链表前一个进程:prev_task(task)

依次访问整个任务队列的能力:for_each_precess(task),每次访问,任务指针都指向链表中的下一个元素

3.3 进程创建

两个函数:fork() evec()

Fork()通过拷贝当前进程创建一个子进程

子进程和父进程的区别:PID、PPID和某些资源和统计量

Exec函数负责读取可执行文件并将其载入地址空间开始运行

3.3.1 写时拷贝

Fork()使用写时拷贝页实现,可以推迟甚至免除拷贝数据,内核让父进程和子进程共享同一个拷贝

资源的复制只有在需要写入的时候才进行,在此之前,只是以只读的方式共享,使地址空间上的页的拷贝被推迟到实际发生写入的时候进行

Fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符

3.3.2 fork()

通过clone()系统调用实现fork()

Fork(),vfork(),_clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork()

Do_fork()定义在kernel/fork.c文件中,调用copy_precess函数,让进程开始运行

3.3.3  vfork()

除了不拷贝父进程的页表外,功能与fork()相同,通过向clone()系统调用传递一个特殊标志来进行

3.4 线程在Linux中的实现

线程机制:提供了在同一程序内共享 内存地址空间运行的一组线程,可以共享打开的文件和其他资源,在多处理器系统上,能够保证真正的并行处理

线程仅仅被视为一个与其他进程共享某些资源的进程,每个线程都拥有唯一隶属自己的task_struct,看起来就像一个普通的进程

对于Linux来说,它只是一种进程间共享资源的手段

3.4.1 创建线程

在调用clone()的时候需要传递一些参数标志来指明需要共享的资源

父子俩共享地址空间、文件系统资源、文件描述符和信号处理程序

传递给clone()的参数标志决定了 新创建进程的行为方式和父子进程之间共享的资源种类,在<linux/sched.h>中定义

3.4.2 内核线程

内核线程没有独立的地址空间,指向地址空间的mm指针被设置为NULL,它只在内核空间中运行,从来不切换到用户空间去,可以被调度,可以被抢占

运行ps –ef命令,可以看到内核线程

内核线程只能由其他内核线程创建,kthreadd内核进程中衍生出所有新的内核线程,在<linux/kthread.h>中申明有接口

新的任务是由kthread内核进程通过clone()系统调用而创建的。新的进程将运行threadfn函数,给其传递的参数为data。进程被命名为namefmt

新创建的进程处于不可运行状态,如果不通过wake_up_process()唤醒它,它不会运行

创建一个进程并让它运行,可以通过kthread_run()来达到

内核线程启动后就一直运行直到调用do_exit()退出,或者内核的其他部分调用kthread_stop()退出,传递给kthread_stop()的参数为kthread_create函数返回的task_struct结构的地址

3.5 进程终结

当一个进程终结时,内核必须释放它所占有的资源并告知其父进程

进程的终结发生在进程调用exit()系统调用时。当进程接受到它既不能处理也不能忽略的信号或异常时,它还可能被动地终结

终结大部分靠do_exit()来完成(定义于kernel/exit.c)

Do_exit()永不返回

之后,进程不可运行并处于EXIT_ZOMBIE退出状态,存在的唯一目的就是向它的父进程提供新消息,父进程检索到信息后,由进程所持有的剩余内存被释放

3.5.1 删除进程描述符

系统还保留了进程描述符,这样做可以让系统有办法在子进程终结后仍能获得它的信息

进程终结时所需的清理工作和进程描述符的删除被分开执行。

在父进程获得已终结的子进程信息后,子进程的task_struct才被释放

Wait():通过唯一的系统调用wait4()来实现,它的标准动作是挂起调用它的进程,直到其中一个子进程退出,此时函数返回该子进程的PID,调用该函数时提供的指针会包含子函数退出时的退出代码

当最终需要释放进程描述符时,release_task()会被调用

3.5.2 孤儿进程造成的进退维谷

父进程在子进程之前退出,必须保证子进程找到新的父进程,否则这些孤儿进程会永远处于僵死状态,白白的耗费内存

解决方法:给子进程在当前进程组内找到一个线程作为父亲,如果不行,就让init做它们的父进程。

在do_exit()中会调用exit_notify(),该函数会调用forget_original_parents(),而后者调用find_new_reaper()来执行寻父过程

遍历所有子进程并为它们设置新的父进程

遍历了两个链表:子进程链表和ptrace子进程链表

解决孤儿问题的方法:在一个单独的被ptrace跟踪的子进程链表中搜索相关的兄弟进程

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

Linux内核分析第三章读书笔记的更多相关文章

  1. Linux内核分析第四章 读书笔记

    Linux内核分析第四章 读书笔记 第一部分--进程调度 进程调度:操作系统规定下的进程选取模式 面临问题:多任务选择问题 多任务操作系统就是能同时并发地交互执行多个进程的操作系统,在单处理器机器上这 ...

  2. Linux内核分析第一二章读书笔记

    linux读书笔记(1,2章) 标签(空格分隔): 20135328陈都 第一章 Linux内核简介 Unix的历史 Unix 虽然已经使用了40年,但计算机科学家仍然认为它是现存操作系统中最强大和最 ...

  3. linux内核分析 第3章读书笔记

    第三章 进程管理 一.进程 1.进程 进程就是处于执行期的程序. 进程就是正在执行的程序代码的实时结果. 进程是处于执行期的程序以及相关的资源的总称. 进程包括代码段和其他资源. 2.线程 执行线程, ...

  4. linux内核分析 第4章读书笔记

    第四章 进程调度 一.抢占与非抢占 1.非抢占式进程调度 进程会一直执行直到自己主动停止运行 2.抢占式进程调度 Linux/Unix使用的是抢占式的方式,强制的挂起进程的动作就叫做抢占. 二.进程优 ...

  5. Linux内核分析第五章读书笔记

    第五章 系统调用 在操作系统中,内核提供了用户进程与内核进行交互的一组接口,这些接口在应用程序和内核之间扮演了使者的角色,保证系统稳定可靠,避免应用程序肆意妄行. 5.1 与内核通信 系统调用在用户空 ...

  6. linux内核分析 第5章读书笔记

    第五章 系统调用 一.与内核通信 系统调用在用户控件进程和硬件设备之间添加了一个中间层,作用有: 为用户空间提供了一种硬件的抽象接口 系统调用保证了系统的稳定和安全 每个进程都运行在虚拟系统中,而在用 ...

  7. Linux内核分析第四章读书笔记

    第四章 进程调度 进程调度程序:确保进程能有效工作的一个内核子程序 决定将哪个进程投入运行,何时运行已经运行多长时间 进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统 原则:只 ...

  8. linux内核分析 第18章读书笔记

    十八章 调试 一.内核调试概述 1.需要面对的 一个确定的bug 一个藏匿bug的内核版本 相关的内核代码的知识和运气 2.艰难的调试工作 重现bug很困难:大部分bug通常都不是行为可靠而且定义明确 ...

  9. linux内核分析 第7章读书笔记——《深入理解计算机系统》

    第七章 链接 --<深入理解计算机系统> 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或拷贝)到存储器并执行. 一.编译器 大多数编译系统提供编译驱动 ...

随机推荐

  1. Scrapy运行流程

    接下来的图表展现了Scrapy的架构,包括组件及在系统中发生的数据流的概览(绿色箭头所示). 下面对每个组件都做了简单介绍,并给出了详细内容的链接.数据流如下所描述. 来源于https://scrap ...

  2. socket.io+angular.js+express.js做个聊天应用(三)

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/www19940501a/article/details/27590611 接着前面博客文章socke ...

  3. SpringMVC+Spring+Mybatis框架集成

    一.基本概念 1.Spring      Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-O ...

  4. 统计单词数 OpenJ_Bailian - 4030(字符串处理)

    一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中 ...

  5. 完美解决centos安装linux后不能上网的问题

    vi / etc /sysconfig/network-scripts/ifcfg-eth0 配置ip地址 DEVICE=eth0 HWADDR=00:0C:29:8C:F7:6F TYPE=Ethe ...

  6. go标准库的学习-net/rpc/jsonrpc

    参考:https://studygolang.com/pkgdoc 导入方式: import "net/rpc/jsonrpc" jsonrpc包实现了JSON-RPC的Clien ...

  7. 华为交换机常用命令(以s5700-SI为例)

    交换机的三种模式: Access模式: 一般用来连接计算机与交换机. 此模式下有一个PVID就是本端口所属的VLAN号,如果从链路上收到无标签的帧,则打上默认VLAN号,然后发给其他端口,如果从链路上 ...

  8. Redis学习笔记--Redis数据过期策略详解==转

    本文对Redis的过期机制简单的讲解一下 讲解之前我们先抛出一个问题,我们知道很多时候服务器经常会用到redis作为缓存,有很多数据都是临时缓存一下,可能用过之后很久都不会再用到了(比如暂存sessi ...

  9. jmeter(三)SOAP/XML-RPC Request

    项目背景:公司的微信端H5界面新开发了会员注册功能,需要对其进行压力测试 项目目标:需要承受每分钟最少6000的压力 一.建立一个测试计划(test plan) 之前有说过,jmeter打开后会自动生 ...

  10. Javascript 对象复制

    如果对象只是一个数据集,可采用json化再反json化的方式克隆一个对象,这个过程会丢失对象的方法.效率比较低. 可以采用如下递归的方式复制一个对象. function clone(target) { ...