进程管理 - d10

回顾:
进程控制
  在Unix/Linux系统中,查看进程的命令:
   ps  -  只能查看当前终端启动的进程
   ps -aux  Linux专用的,Unix系统不直接支持
   ps -ef  通用的

   某些Unix系统有两个ps命令,可以用whereis 查看一下。
   kill -9 进程的PID        杀死对应的进程
   其实就是发送9信号给对应进程

  如何创建子进程?
       fork()  - 通过复制 父进程创建子进程
       vfork() + execl()  - 不复制父进程的任何东西,创建子进程,子进程会执行完全独立的代码(与父进程无关)
       fork() 会复制除了代码区之外的所有内存区域,代码区和父进程共享。如果父进程有文件描述符,子进程会复制描述符,但不复制文件表(父子进程使用相同的文件表)。子进程也会复制父进程的输入输出缓冲区。
今天:
  wait()和waitpid() 让父进程等待子进程的结束进程结束的方式
  vfork()和execl()
  信号
  进程的结束方式
     正常结束
       1 在main()函数中,执行了return语句(没有写的,系统会在最后自动加上return语句,返回内存中的任意值)
       2 执行exit(int)函数(任意函数中都可以),exit()中的参数是退出状态码,一般是用非负数代表功能已经完成,用负数代表功能没有完成
       3 _exit() 或 _Exit() 也能退出进程
       4 最后一个线程正常结束
     非正常结束
       1 被信号终止
       2 最后一个线程被其他线程取消
     exit() 、 _Exit()和_exit()的区别
       1 _Exit()和_exit()几乎没有区别,_Exit()是标C函数,另外一个是Unix C函数。
       2 _Exit()是立即退出,不会有多余的时间。
       3 exit()不是立即退出,甚至可以执行另外一个函数后再退出。另外的函数可以用atexit()注册。

     函数wait()和waitpid()可以让父进程等待子进程的结束,从而取到子进程的退出状态码(exit()或return)。
     wait()函数等待任意一个子进程的结束,没法指定等待哪个子进程;也必须以阻塞的方式等待。
     waitpid()更加灵活,可以指定等待哪个/哪些 子进程,甚至可以设置以 非阻塞的方式等待(可以不等)。

   pid_t waitpid(pid_t pid , int* status, int options)
       函数功能:让父进程等待子进程的结束,并取得退出码。
       参数: pid就是父进程等待哪个/哪些 子进程
                 >0 -  等待 进程ID 等于 pid的那个子进程
                 -1 -  等待任意一个子进程
                  0 -  等待本组子进程(和父进程同一个进程组的)
                <-1 -  等待进程组id=-pid的子进程
             status 可以取得结束子进程的退出情况和退出状态码,需要借助宏函数实现数据的筛选(WIFEXITED() 和 WEXITSTATUS())
             options 可以设置是否等待,0 代表等待,WNOHANG 不等待。
        返回: 大于0 就是 结束子进程的PID
               0 就是WNOHANG时 没有子进程结束直接返回 0
              -1 代表出错。
     注: wait()和waitpid() 都可以回收僵尸子进程的资源。(殓尸工)

  vfork() - 从语法上说,vfork()和fork() 没有任何的区别。区别在于vfork() 不会复制 父进程的任何资源,子进程会直接占用父进程的资源,
           父进程被阻塞(暂停运行), 只有在以下两种情况下父进程才会解除阻塞继续运行:
               1 子进程运行结束,把资源还给父进程,父进程继续运行。
               2 子进程执行了 exec 系列函数(比如execl()),也会把资源还给父进程,子进程将加载 execl() 所指定的进程资源。
              其中,情况1 是没有现实意义。

  fork() 之后 父子进程 谁先运行不一定,vfork() 之后一定是子进程先运行(父进程没资源)。

  注: vfork()创建的子进程,如果没有execl()或execl()失败,一定要用exit() 函数 显式退出。否则可能导致出错或者死循环。

  vfork()+execl() 创建和执行子进程,组合方式如下:
       vfork() 负责启动新的子进程,但不负责提供子进程的代码和数据。
       execl() 无法启动新进程,但可以提供新的代码和数据 供当前进程更新。

   int execl(char* path, char* cmd, ...)
       函数功能: 启动一个全新的程序,替换当前的进程
       参数:     path 就是 新程序在硬盘对应的文件路径
                  cmd 就是 执行程序的命令
                  ... 可以包括 选项、参数,以NULL 作为 结束。
                       execl("/bin/ls","ls","-l",NULL)   调用系统的ls
       返回: 成功就 执行全新的代码,因此 无返回。失败返回 -1 。
              失败最可能的情况就是 第一个参数写错了。

  练习: vfork()+execl()的用法
       用vfork()+execl()方式创建子进程,其中子进程实现的功能是:
       在 execl()之前打印子进程 PID,然后在子进程中启动新的程序,新的程序功能 也是打印 当前进程的PID (这两个PID应该是一样的)。
       提示:需要写两个程序,连接成两个不同的名字。


  重点在于 如何创建子进程,fork() 和 vfork()+execl() 。
信号(signal)
  在Unix系统中,信号是最常见的 软件中断方式之一。大多数的 软件中断都借助信号实现。
  中断 是中止当前正在执行的程序,转而执行其他的代码。中断分为硬件中断和软件中断,一般可控的 都是采用软件中断。有些硬件的故障也会转换成软件中断进行处理,一般都是信号。
  信号 本质是非负整数,Unix系统 信号是 0-48,Linux系统是0-64,中间不保证连续。其中,信号0 有特殊的用途,没有 实际的意义。真正使用时,信号 从1 开始。
  命令 kill 就是用来处理信号的命令,kill 可以给进程发信号。
      $ kill -l 可以查看 都有哪些信号。
      每个信号除了值,都还有一个宏名称,比如:信号2 叫SIGINT,
      所有的宏名称都以 SIG 开头。信号分为两类,可靠信号和不可靠信号。
          不可靠信号 就是 1-31 ,特点是 不支持排队,因此多次发动时可能丢失。
          可靠信号   就是 34-64 ,特点是 支持排队,因此不会丢失。
     使用信号编程时,用宏名称有更好的通用性。

 信号的处理方式 (默认、忽略和自定义) - 重点
    1 当信号到来时,如果不做任何的设置,将采用默认处理。默认处理一般 就是退出进程。
    2 忽略处理,当该信号没有来过,不做任何的处理。
    3 自定义处理,程序员按照自己的方式,写函数处理信号。(必须会)
 注: 不是所有的信号都能忽略和自定义的,信号9 只能采用默认处理
 每个用户只能给自己的进程发信号,其他用户的进程无权限发送信号。但root 用户可以给所有的用户进程发信号。

 Unix系统/Linux系统提供了设置信号处理方式的函数:
      signal()  -  功能简单,但实用主讲
      sigaction() - 功能复杂,有些功能用不到

 void (*fa) (int)  signal(int signum,  void (*fa)(int) )
  函数功能:指定某个信号的处理方式
   参数: signum 就是 哪个信号,一般用宏名称
          第二个参数是一个函数指针,支持三个值:
                1 SIG_IGN 处理方式为 忽略
                2 SIG_DFL 处理方式为 默认
                3 自定义一个函数,把函数名放在这里 (自定义处理)
 返回: 正常会返回之前的信号处理方式,失败返回 SIG_ERR。