第三章 进程管理

3.1 进程

进程是处于执行期的代码。通常进程还要包含其他资源,像打开的文件、挂起的信号、内核的内部数据、处理器状态、一个或多个具有内存映射的内存地址空间及一个或多个执行线程,当然还包括用来存放全局变量的数据段等。

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

通常,创建新的进程都是为了立即执行新的、不同的程序,而接着调用exec()这组函数就可以创建新的地址空间,并把新的程序载入其中。

3.2 进程描述符以及任务结构

内核把进程的列表存放在叫做任务队列(task list)的双向链表中。链表中的每一个项都是类型为task_struct(processdescriptor 进程描述符)的结构。该结构定义在<linux/sched.h>中。

进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件、进程的地址空间、挂起的信号、进程的状态,还有其他更多信息。

3.2.1分配进程描述符

Linux通过slab分配器分配task_struct结构,各个进程的task_struct存放在它们内核栈的尾端,这样做是为了让那些像x86那样寄存器较少的硬件体系结构只要通过栈指针就能计算出它的位置。

struct thread_info 在文件<asm/thread_info.h>中定义:

struct thread_info {
      struct task_struct       *task;      
struct exec_domain    *exec_domain;     
       unsigned int        flags;            
       unsigned int        status;
 unsigned int              cpu; 
     int                 preempt_count;
       mm_segment_t           addr_limit;   
         struct restart_block    restart_block;
void *sysenter_return;
int uaccess_err;
      
};

3.2.2进程描述符的存放

内核通过一个唯一的进程标识值(process identification value)或PID来标识每个进程。PID是一个数,表示为pid_t隐含类型,实际上就是一个int类型。PID的最大值默认设置为32768,这受<linux/threads.h>中所定义PID最大值的限制。如果确实需要的话,可以不考虑与老式系统的兼容,由系统管理员通过修改/proc/sys/kernel/pid_max来提高上限。

3.2.3进程状态

进程描述符中的state域描述了进程的当前状态,该域的值也必为下列五种状态之一:

  • TASK_RUNNING(运行)—进程是可执行的。
  • TASK_INTERRUPTIBLE(可中断)—进程正在睡眠,等待某些条件的达成。
  • TASK_UNINTERRUPTIBLE(不可中断)
  • TASK_TRACED—被其他进程跟踪的进程。
  • TASK_STOP(停止)—进程停止执行。

3.2.4设置当前进程状态

内核经常需要调整某个进程的状态,这时最好使用set_task_state(task,state)函数。参看<linux/sched.h>中对这些相关函数实现的说明。

3.2.5进程上下文

一般程序在用户空间执行。当一个程序执行了系统调用或者触发了某个异常,它就陷入了内核空间。此时,我们称内核“代表进程执行”并处于进程上下文中。

3.2.6进程家族树

Unix系统的进程之间存在一个明显的继承关系,所有的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动Init进程。该进程读取系统初始化脚本(initscript)并执行其他的相关程序,最终完成系统启动的整个过程。

 

每个task_struct都包含一个指向其父进程task_struct、叫做parent的指针,还包含一个称为children的子进程链表。

获得父进程的进程描述符:Struct task_struct *my_parent = current->parent

访问子进程:

Struct task_struct *task;
Struct list_head *list;
 
List_for_each(list, &current->children){
       Task = list_entry(list,struct task_struct, sibling);
       /*task指向当前某个子进程*/
}

获取下一个进程和前一个进程:

Task = list_entry(task->tasks.next, struct task_struct, tasks);
Task = list_entry(task->tasks.perv, struct task_struct, tasks);

3.3 进程创建

分解到两个单独的函数中去执行:fork()和exec()。

  • 首先,fork()通过拷贝当前进程创建一个子进程,子进程与父进程的区别仅仅在于PID、PPID和某些资源和统计量。
  • exec() 函数负责读取可执行文件并将其载入地址空间开始运行。

3.3.1写时拷贝

Linux的fork()使用写时拷贝(copy-on-write)页实现。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候才进行。

3.3.2fork()

Linux中通过clone()系统调用实现fork()。Fork()、vfork()、和__clone()库函数根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork()

     

do_fork完成了创建中的大部分工作。该函数调用copy_process(),然后让进程开始运行。

3.3.3vfork()

vfork()的好处就仅限于不拷贝父进程的页表项。

3.4线程在linux中的实现

Linux把所有的线程都当做进程来实现。线程仅仅被视为一个与其它进程共享某些资源的进程。每个线程都拥有惟一隶属于自己的task_struct

3.4.1创建线程

线程的创建和普通进程的创建类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SINHAND , 0);

普通的fork()的实现:clone(SIGCHILD, 0);

vfok()的实现:clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0);

 

clone使用的参数标志定义在<linux/sched.h>中。

3.4.2内核线程

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

内核线程也只能由其他内核线程创建。内核是通过从kthreadd内核进程中衍生出所有新的内核线程来自动处理这一点的。在<linux/kthread.h>中申明有接口,从现有内核线程中创建一个新的内核线程的方法:

struct task_struct *kthread_create(int (*threadfn)(void *data),
                               void *data,
                               const char namefmt[],
...)

新的任务是由kthread内核进程通过clone()系统调用而创建的。穿件一个进程并让它运行起来,可以通过调用kthread_run()来达到:

struct task_struct *kthread_run(int (*threadfn)(void *data),
                               void *data,
                               const char namefmt[],
...)

内核线程启动后就一直运行直到调用do_exit()退出,或者内核的其他部分调用kthread_stop()退出。

3.5 进程终结

一般来说,进程的析构是自身引起的。它发生在进程调用exit()系统调用时,既可能显式地调用这个系统调用,也可能隐式地从某个程序的主函数返回。不管进程是怎么终结的,该任务大部分都要靠do_exit()来完成。

至此,与进程相关联的所有资源都被释放掉了。进程不可运行并处于EXIT_ZOMBIE退出状态。它占用的所有内存主要就是内核栈、thread_infotask_struct结构。此时进程存在的唯一目的就是向它的父进程提供信息。父进程检索到信息后,或者通知内核那是无关的信息后,由进程所持有的剩余内存被释放,归还给系统调用。

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

对于孤儿进程的解决办法:

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

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

Linux内核设计与实现第六周读书笔记的更多相关文章

  1. LINUX内核设计与实现第三周读书笔记

    LINUX内核设计与实现第三周读书笔记 第一章 LINUX内核简介 1.1 Unix的历史 1969年的夏天,贝尔实验室的程序员们在一台PDR-7型机上实现了Unix这个全新的操作系统. 1973年, ...

  2. Linux内核设计与实现第十周读书笔记

    第十七章 设备与模块 关于设备驱动与设备管理,我们讨论四种内核成分. 设备类型 模块 内核对象 sysfs 17.1设备类型 在Linux以及所有Unix系统中,设备被分为以下三种类型: 块设备,块设 ...

  3. Linux内核设计与实现第八周读书笔记

    第四章 进程调度 进程在操作系统看来是程序的运行态表现形式. 4.1多任务 多任务操作系统就是能同时并发地交互执行多个进程的操作系统. 多任务操作系统会使多个进程处于堵塞或者睡眠状态.这些任务尽管位于 ...

  4. Linux内核设计与实现第五周读书笔记

    第十八章 调试 18.1准备开始 需要的只是: 一个确定的bug.大部分bug通常都不是行为可靠而且定义明确的. 一个藏匿bug的内核版本. 相关的内核代码的知识和运气. 18.2内核中的bug 内核 ...

  5. linux内核设计与实现第七周读书笔记

    第七章 链接 链接(linking)是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储并执行.链接可以执行于编译时(compile time),也就是在源代 ...

  6. 《Linux内核设计与实现》Chapter 5 读书笔记

    <Linux内核设计与实现>Chapter 5 读书笔记 在现代操作系统中,内核提供了用户进程与内核进行交互的一组接口,这些接口的作用是: 使应用程序受限地访问硬件设备 提供创建新进程与已 ...

  7. 《Linux内核设计与实现》Chapter 18 读书笔记

    <Linux内核设计与实现>Chapter 18 读书笔记 一.准备开始 一个bug 一个藏匿bug的内核版本 知道这个bug最早出现在哪个内核版本中. 相关内核代码的知识和运气 想要成功 ...

  8. 《Linux内核设计与实现》Chapter 3 读书笔记

    <Linux内核设计与实现>Chapter 3 读书笔记 进程管理是所有操作系统的心脏所在. 一.进程 1.进程就是处于执行期的程序以及它所包含的资源的总称. 2.线程是在进程中活动的对象 ...

  9. 《Linux内核设计与实现》第四周读书笔记——第五章

    <Linux内核设计与实现>第四周读书笔记--第五章 20135301张忻 估算学习时间:共1.5小时 读书:1.0 代码:0 作业:0 博客:0.5 实际学习时间:共2.0小时 读书:1 ...

随机推荐

  1. C# webapi 路由规则和接收数据

    1:新建的web api项目 默认的访问api方式:  (get,post,delect,put)  api+控制器  以Post为例子 post提交单个参数: 接收方法  post提交多个参数  接 ...

  2. Android开发笔记——视频录制播放常见问题

    本文分享自己在视频录制播放过程中遇到的一些问题,主要包括: 视频录制流程 视频预览及SurfaceHolder 视频清晰度及文件大小 视频文件旋转 一.视频录制流程 以微信为例,其录制触发为按下(住) ...

  3. 一步步带你配置IIS(包括错误分析)

    今天趁着工作中的问题一下子来解决IIS配置 发布网站:点击VS发布网站 第一步:新建配置文件(我取名为webSite) : 第二步:选择发布方法并且选择把文件发布到哪里(比喻在D盘创建一个文件夹web ...

  4. Spring学习(七)-----Spring Bean的5种作用域

    在Spring中,bean作用域用于确定哪种类型的 bean 实例应该从Spring容器中返回给调用者.bean支持的5种范围域: 单例(singleton) - 每个Spring IoC 容器返回一 ...

  5. ffmpeg 踩坑实录 近期使用总结(三)

    一.背景介绍 将ffmpeg运用到项目上已经有一段时间了,趁现在有空赶紧记下来. 二.技术点总结    2.1 实现方式 项目里面主要运用的形式是,在java端,调用操作系统的方法,并执行切片命令. ...

  6. 利用 Intel Realsense做SLAM开发(一)

    最近手里拿到一台Realsense D435,就是这个: https://click.intel.com/intelr-realsensetm-depth-camera-d435.html 所以准备拿 ...

  7. MySQL☞大结局

    emmm,看了这么多大概会用了点点,学到了一点点 select  列名/*/聚合函数 from  表名1 别名1  连接查询(左外.右外等等) 表名2 别名2 on 关联条件 where 查询条件 g ...

  8. Teaching Machines to Understand Us 让机器理解我们 之一 引言

    Teaching Machines to Understand Us   By Tom Simonite  MIT Technology Review Vol.118 No.5 2015 让机器理解我 ...

  9. Redis 总结精讲 看一篇成高手系统4

    本文围绕以下几点进行阐述 1.为什么使用redis2.使用redis有什么缺点3.单线程的redis为什么这么快4.redis的数据类型,以及每种数据类型的使用场景5.redis的过期策略以及内存淘汰 ...

  10. windows64系统下安装 redis服务 (详细)

    Linux下Redis安装链接 :     转到 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表) ...