Linux内核探讨-- 第一章

      文是个人分析《Linux内核设计与实现》而写的总结,欢迎转载,请注明出处:

                                                                                 http://blog.csdn.net/dlutbrucezhang/article/details/12139579

        第一章--进程管理

1.什么是进程     

       第一章首先从进程开始讲起,进程是操作系统最基本的东西,我们依靠进程提供服务,利用进程完成我们需要完成的工作。首先需要明白什么是进程。它是一个动态实体,是程序的一次执行过程。一个进程需要一些必须的资源才能够运行,而最基本的资源就是 CPU 和内存。进程获得 CPU 它就处于运行状态,进程的运行肯定需要程序段,数据段,堆,栈这样的资源,而这些就是内存提供的。在Linux中的实现是两种虚拟机制,被称作是“虚拟处理器”和“虚拟内存”。虚拟处理器的概念就好像是每个进程独占CPU,虚拟内存的概念是每个进程拥有自己的内存地址空间,而这个地址空间涵盖了整个内存空间。进程的创建工作是通过 fork() 函数,进程的终止是通过 wait4() 函数,这些内容的详细介绍将在下文提及。


2.进程描述符以及进程的结构

      进程描述符用于描述一个进程,在其中存储了一个进程的所有信息,每个进程都有一个进程描述符。而所有的进程描述符都被存储在一个双向循环链表中。由此,我们可以想到,从链表中的任何一个进程开始,我们可以遍历系统的所有进程。进程描述符比较重要的字段有 :进程PID,进程的父进程,进程的状态等等。进程描述符是由 slab 分配器分配的,它是作为页高速缓存特殊存在的一种结构。当一个进程创建时,slab 分配器分配一个进程描述符给它,当进程消亡时,进程描述符被 slab 回收,可以循环利用。

      和进程描述符密切相关的是一个结构--thread_info。这个结构的定义如下:

 union thread_union {
      struct thread_info thread_info;
     unsigned long stack[THREAD_SIZE/sizeof(long)];
  };

      我们可以看到,它是和一个栈存储在一起的,这个栈被称作是内核栈。thread_info 的位置在内核栈的尾端(往低地址方向增长)。thread_info 中存储了进程描述符的地址,所以,我们可以很方便的获得当前进程的描述符,这对于 X86 架构的计算机是非常优良的设计,因为我们可以不占用昂贵的寄存器来存储地址。

      说了这么多,好吧,下面这张图更能显示它们之间的关系:


3.进程状态

      进程是一个活动的实体,当然,它是有生命的,所以,它就必然会有生命中的各个状态。一般情况下,进程的状态会被划分为五种。

      1.TASK_RUNNING 进程处于就绪态或者是运行态。这个状态很有意思,它可以代表进程的两种很相似的状态。这两种状态的区别是进程时候获得 CPU 的使用权,如果获得了,那么进程处于运行态,反过来,如果没有,那么进程就处于就绪态。

       2.TASK_INTERRUPTIBAL 进程处于可中断状态,此时进程由于某些情况不能运行而被挂起,这时会引起进程的切换,当进程希望的事件到来或者接收到信号时,进程就有可能会被唤醒,等待重新被调度执行。

      3.TASK_UNINTERUPTIBLE 进程处于不可中断状态,从命名方式我们就可以看出,它和上一种进程状态是很相似的,的确,它们在表现上也是极为相似的,都是由于进程期待的事情没有发生而被挂起,不能运行。但是,这种状态的进程不能被信号唤醒,所以,我们不能发送一个终止信号给进程使进程死掉,所以,这种状态的进程并不多,一个常见的例子就是,进程等待磁盘的数据传输。

      4.TASK_PTRACED 进程处于被跟踪的状态,这时,会发生一些很有意思的事,进程的父进程会变成跟踪进程,当然,这只是临时的,此时,进程描述符的 parent 字段会被修改。

      5.TASK_STOPPED 进程处于死亡状态,这时的进程不能再被投入运行,它此刻拥有的资源也仅仅是 进程描述符,内核栈,thread_info 结构,此时,进程会发送信号给父进程,等待父进程收回它的资源。

4.进程上下文

      进程上下文其实就是进程切换的过程,就是指进程的所有信息。

5.进程家族树

      我们都知道,类UNIX系统组织系统中的文件都是以树的形式完成的,根则是文件系统的根目录,之后是子目录,接着是子目录的子目录。。。,最后才是文件。由于这种思想能够很好的管理计算机资源,所以,系统中的进程也是以这种方式来管理的。进程被组织成一棵树,树的根是进程1 ,也就是 init 进程。

6.进程的创建

      熟悉 Linux 编程的朋友都知道一个函数 -- fork(),没错,这就是著名的创建进程的函数,其实,其内部实现是 clone() 函数,并给它传递相应的参数完成所需要的进程或者线程。

      fork() :如果我们使用的是这个函数,那么 shell 会创建一个普通的进程,并给它分配进程描述符等资源。Linux 中有一项非常著名的技术,被称之为写时拷贝,它的意思是,父进程和新创建的子进程共享父进程的地址空间,但是这时,地址空间是只读的,也就是说,此时,如果有一个进程往地址空间中写,那么系统就会拷贝这一页,并让需要写的那个进程在拷贝的那一页完成写操作。这项技术的原因是因为,进程创建的子进程一般用于其他的功能,所以,它会很快的调用 exec() 函数,这时会创建新的地址空间,如果,之前创建时直接拷贝,那么原来拷贝的东西就需要被丢弃,所以,浪费了许多资源和空间。但是,它还是需要拷贝父进程的页表。

      vfork():这个函数和 fork 函数是非常相似的,它只是没有拷贝父进程的页表,但是,在函数返回后,父进程被阻塞,子进程会运行,直到它有了自己的地址空间或者是调用 exit 退出执行,才能恢复父进程的运行。

7.线程在 Linux 中的实现

      在Linux系统中,线程其实和进程没有很多的区别,无非是线程没有自己的地址空间,线程是调度执行的基本单位,而进程是资源拥有和分配的基本单位。Linux 在创建线程时,无非是给新创建的线程分配一个进程描述符,并把它的 mm 字段指向创建它的那个进程,由此,完成线程共享进程的地址空间。

8.内核线程

      有些情况,需要内核提供功能并定期的执行,这时,内核线程肯定是最好的选择。既然被称为是内核线程,所以,它肯定是在内核空间运行,而且只是在内核空间运行,所以,我们可以得出结论,它的 mm 字段被赋予 NULL (由于也是线程,所以,肯定存在进程描述符)。在Linux系统上,我们可以利用命令  ps -ef 查看你的系统上存在多少内核线程,看看它们是什么,在这里告诉你,有很多。

9.进程终结

      进程在执行了所需要完成的功能,或者是显示的调用了 exit 之后,就会走向一条面向死亡的不归路。这时,操作系统会收回进程的地址空间,CPU,信号,打开的文件等资源。此时,进程的状态变成了 TASK_ZOMBIE,僵尸进程。这时,由于进程还有一些资源没有被系统回收,而且没有被父进程收回,变成了没人管的进程。这时,会有两种情况会发生。

      1.如果,进程的父进程存在,那么子进程会给父进程发送信号,父进程要么收回子进程的资源,要么不在意子进程的状态,直接丢弃。

      2.如果,进程的父进程已经死了,那么这时的子进程又成了孤儿进程,这时,就需要给子进程寻找一个父进程,这时,又分为两种情况:

                  1.如果,子进程所在的线程组还有进程存在,那么就从线程组中寻找一个进程,让它成为子进程的父进程 

                  2.如果,子进程所在的线程组中没有存活的进程,那么,就让 init 进程成为它的父进程

        我们此时让然需要考虑一种情况,就是进程如果被跟踪了呢?那么这个进程的父进程是临时的,而它真实的父进程已经死了,所以,我们又得为被跟踪的子进程寻找父进程,依然是重复上述的两步。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值