美文网首页
《操作系统概念精要》基本概念整理之进程篇(二)

《操作系统概念精要》基本概念整理之进程篇(二)

作者: 小pb | 来源:发表于2019-10-29 14:58 被阅读0次

    上一章讲到程序的状态,以及调度的实质,这一章主要将程序的在运行过程中的具体执行过程,包括进程的创建,终止。

    进程的运行

    进程的创建

    进程在执行的过程中可以创建多个新的进程。创建进程成为父进程,而新的进程成为子进程。每个子进程又可以创建其他进程,从而形成进程树。下图为一个Linux系统的典型的进程树。

    process_tree.png

    在linux 下可以通过 pstree 查看进程树。


    linux_process_tree.png

    父子进程

    一般来说,当一个进程创建子进程时,该子进程会需要一定的资源来完成任务。子进程可以从操作系统那里直接获取资源,也可以只从父进程里获得。父进程可能在子进程之间非配资源或者共享资源(主要是打开的I/O设备,也有可能是内存)。

    在进程创建新进程时,可能有两种情况:

    • 父进程与子进程并发进行。
    • 父进程等待,直到某个子进程执行完成。

    新进程的地址空间也有两种可能:

    • 子进程是父进程的复制品。
    • 子进程加载另外一个程序。

    这里看下Unix和Linux的具体实现吧。
    每个进程都有一个唯一的整型进程标识符来标识。进程是通过fork()系统调用来创建的。每个进程的地址空间复制了原来的父进程的地址空间。这种机制允许父子进程之间可以轻松的通信。
    这两个进程都继续执行fork()之后的指令。但是对于子进程,系统调用fork()的返回值是0,对于父进程,返回的是子进程的进程标识符。(具体实现参考《linux 内核源码剖析》) 根据返回返回值的不同对父子进程进行不同的操作。

    通常fork()系统调用之后,子进程一般会使用exec()系统调用,以新的程序来进行替换新进程的内存空间,系统调用exec()加载二进制文件到内存中(破坏包含系统调用exec()的原来进程内存内容),并开始执行新的进程。
    父进程能够创建更多的子进程,或者如果在子进程运行时没有什么可做,那么他就会使用系统调用*wait() * 把自己移出就绪队列,等待子进程完成,这里是任意一个子进程。

      #include <sys/types.h>      
      #include <sys/wait.h>                                                   
      #include <stdio.h>                                                                       
      #include <unistd.h>                                                                      
                        
      int main() {                                                                             
         pid_t pid;                                                                                                                                                                
         // 调用fork系统调用创建新的进程                                                      
         pid = fork();                                                                        
                                                                                             
         if (pid < 0) {                                                                       
             fprintf(stderr, "Fork Failed.\n");                                               
             return 1;                                                                        
         } else if (pid == 0) {                                                               
             printf("I am child process.\n");                                                 
             execlp("/bin/ls", "ls", NULL);                                                   
         } else {                                                                             
             wait(NULL);                                                                      
             printf("I am parent process.\n");                                                
        }                                                                                    
        return 0;                                                                            
    } 
    

    执行命令: ./fork


    fork.png

    进程的终止

    当进程完成执行最后语句并通过系统调用exit()请求操作系统删除自身时,进程终止。这时,进程可以返回状态值到父进程(如果父进程调用了wait())。所有进程资源,如物理和虚拟内存,文件表,I/O缓冲区等,都由操作系统释放。
    在其他情况下,进程也有可能出现进程终止。进程通过适当的系统调用,可以终止另一个进程。通常,只有终止进程的父进程才能执行这一系统调用,否则用户可以任意终止彼此的进程。

    父进程终止子进程的原因有很多,如:

    • 子进程使用了超过他分配的资源
    • 分配给子进程的任务,不再需要
    • 父进程正在退出,而且有些系统不支持无父进程的子进程继续运行。
      第三种的情况,是在某些系统中存在,那么,父进程创建的所有的子进程都应该终止,比如关机,这种株连九族的现象叫做 级联终止,一般由操作系统来启动。

    在Unix和Linux系统中,父进程可以通过系统调用wait() 等待子进程的终止,系统调用wait()可以通过参数,让父进程获取子进程的终止状态;wait() 也返回终止子进程的标识符,让父进程知道哪个子进程被终止了。

      pid_t pid;    
      int status;  
      pid = wait(&status); 
    

    当一个进程终止时, 操作系统会释放其资源,不过,它位于进程表中的条目还是在的,知道父进程调用wait(); 这是因为进程表包含了进程的退出状态。

    当进程已经终止,父进程没有wait()的进程,称为 僵尸进程
    所有进程在中的时候都会进入到这个状态,但一般僵尸进程只是很短暂的存在,一旦父进程调用了wait(), 僵尸进程的进程描述符和它的进程表中的条目都会被释放。

    如果父进程没有执行wait()就终止了,那么它的子进程就变成了孤儿进程, 在Unix和Linux 系统,他们的父进程就会变成 pid = 1的init 进程。 进程init 会定期进行wait() 操作。以便收集孤儿进程的状态,并释放孤儿进程标识符和进程表条目。

    相关文章

      网友评论

          本文标题:《操作系统概念精要》基本概念整理之进程篇(二)

          本文链接:https://www.haomeiwen.com/subject/fvuwvctx.html