进程的创建和退出

1、进程的创建
        创建进程有两种方式,一是由操作系统创建,二是由父进程创建。由操作系统创建的进程,它们之间是平等的,一般不存在资源继承关系,而对于由父进程创建的进程(通常称之为子进程),它们和父进程存在隶属关系,子进程又可以创建进程,这样形成一个进程家族,子进程可以继承其父进程几乎所有的资源。在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。
        每个进程由进程ID号标识,进程被创建时会为其分配一个唯一的进程ID,一个现有的进程可以调用fork函数创建一个新进程。

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程中返回子进程的ID

        一般情况下,函数最多有一个返回值,但fork函数非常特殊,它有两个返回值,即调用一次,返回两次,两次返回的唯一区别是子进程返回0,父进程返回子进程的ID。将子进程ID返回给父进程的理由是:一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到的返回值为0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID(进程ID 0总是由内核交换进程使用,所有子进程的进程ID不可能为0)。根据返回值的不同来区分父、子进程。
        成功调用fork函数后,当前进程实际上已经分裂为两个进程,一个是原来的父进程,另一个是刚刚建立的子进程。fork函数返回两次的前提是进程创建成功,如果进程创建失败,则只返回-1。创建一个子进程之后,父进程和子进程争夺CPU资源,抢到CPU资源的进程执行,另一个进程挂起等待,如果想要父进程等待子进程执行完毕后在继续执行,可以在fork操作之后调用wait或waitpid。一般来说,fork之后是父进程先执行还是子进程先执行是不确定的,这这取决于内核所使用的调度算法。
        vfork函数也可以用来创建一个新进程,与fork函数相比,它有自己独特的用处,以下是两者的异同:

    (1)vfork函数和fork函数都是调用一次,返回两次。
    (2)使用fork函数创建一个子进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性,而使用vfork函数创建一个子进程时,操作系统并不会将父进程的地址空间完全复制到子进程,用vfork函数创建的子进程共享父进程的地址空间,也就是说子进程完全运行在父进程的地址空间上,子进程对该地址空间中任何数据的修改父进程都可以看见。
    (3)使用fork函数创建一个子进程时,那和进程先运行取决于系统的调度算法,而vfork函数创建一个进程时,子进程先运行,当它调用exec或exit之后,父进程才可能被调度运行,如果在调用exec或exit之前子进程要依赖父进程的某个行为,就会导致死锁。
    (4)使用调用fork函数创建一个子进程时,子进程需要将父进程几乎每种资源都要赋值,所以fork函数是一个开销很大的系统调用,这些开销并不是所以情况都需要的,比如fork一个进程后,立即调用exec执行另外一个应用程序,那么fork过程中子进程对父进程地址空间的复制僵尸一个多于的过程,vfork函数创建一个子进程时不会拷贝父进程的地址空间,这挨打减小了系统开销。

        使用fork函数创建一个子进程时时,子进程会继承父进程的很多属性,主要包括用户ID、组ID、当前工作目录、根目录、打开的文件、创建文件时使用的屏蔽字、信号屏蔽字、上下文环境、 共享的存储段、资源限制等。子进程与父进程有一些不同的属性,主要有如下这些:
    (1)子进程有它自己惟一的进程ID。
    (2)fork的返回值不同,父进程返回子进程的ID,子进程返回0。
    (3)不同的父进程 ID。子进程的父进程ID为创建它的父进程ID。
    (4)子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符。
    (5)子进程不继承父进程设置的文件锁。  
    (6)子进程不继承父进程设置的警告。
    (7)子进程的未决信号集被清空。
        下面我们来讨论一下以下三个问题:
    (1)子进程是在父进程调用fork后生成的,子进程在其结束时需要将其终止状态返回给父进程,但是如果父进程在子进程之前终止,则将如何呢?
        对于父进程已经终止的所有进程,它们的父进程都改变为init进程,我们称这些进程由init进程领养。其操作过程大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,则将该进程的父进程ID更改为1 (init进程的ID),这种处理方法保证了每个进程都有一个父进程。
    (2)如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?
        内核为每个终止的子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID、该进程的终止状态、以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占用的资源)的进程被称为僵死进程。
    (3)一个由init进程领养的进程终止时会发生什么?它会不会变成一个僵死进程?
        不会变成一个僵死进程,因为init被编写成无论何时只要一个子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。当提及“一个init的子进程”时,这指的可能是init直接产生的进程,也可能是其父进程已终止,由init领养的进程。
        孤儿进程:如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿进程,它由init进程收养,成为init进程的子进程。
        守护进程:守护进程是指在后台运行的、没有控制终端与之相连的进程,它独立于控制终端,通常周期性地执行某种任务。守护进程常常在系统启动时自启动,仅在系统关闭时才终止,其生存周期较长。
    可通过ps -axj命令查看常用的系统守护进程,其中最为常见的init进程负责个运行层次间的系统服务。

2、进程的退出
         进程退出表示进程即将结束运行,在Linux系统中进程退出的方法分为正常退出和异常退出两种。其中正常退出的方法有3种,异常退出的方法有两种
    (1)正常退出
        1> 在main函数中执行return。
        2> 调用exit函数。
        3> 调用_exit函数。
    (2)异常退出
        1> 调用 abort函数。
        2> 进程收到某个信号,而该信号使程序终止。
        不管是哪种退出方式,最终都会执行内核中的同一段代码,这段代码用来关闭进程所有已打开的文件描述符,释放它所占用的内存和其他资源。以下是各种退出方式的比较
     (1)exit和return的区别:exit是一个函数,有参数,而retun是函数执行完后的返回。exit把控制权交给系统,而return将控制权交给调用函数。
     (2)exit 和abort的区别:exit是正常终止进程,而abort是异常终止。
     (3)exit(int exit_code):exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生,比如溢出、除数为0。
     (4)exit()和_exit()的区别:exit 在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中,两个函数均能正常终止进程,但是_exit()会执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。
        父子进程终止的先后顺序不同会产生不同的结果,如下所示:
    (1)在子进程退出前父进程先退出,则系统会让init进程接管子进程。
    (2)当子进程先于父进程终止,而父进程又没有调用wait函数等待子进程结束,子进程进入僵死状态,并且会一直保持下去除非系统重启,子进程处于僵死状态时,内核只保存该进程的一些必要信息以备父进程所需,此时子进程始终占用着资源,同时也减少了系统可以创建的最大进程数。
    (3)如果子进程先于父进程终止,且父进程调用了wait或waitpid函数,则父进程会等待子进程结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值