首页 > 系统 > Unix > 正文

《Unix环境高级编程》读书笔记 第8章-进程控制

2024-06-28 13:24:24
字体:
来源:转载
供稿:网友
《Unix环境高级编程》读书笔记 第8章-进程控制 1. 进程标识
  • 进程ID标识符是唯一、可复用的。大多数Unix系统实现延迟复用算法,使得赋予新建进程的ID不同于最近终止所使用的ID
  • ID为0的进程通常是调度进程,也常被称为交换进程。它是内核的一部分,是系统进程。
  • ID为1的进程通常是init进程,在自举过程结束时由内核调用。该进程负责在内核自举后启动一个Unix系统,它决不会终止,是一个普通的用户进程,但以超级用户特权运行。
  • ID为2的进程是页守护进程,负责支持虚拟存储器系统的分页操作。
#include <unistd.h>pid_t getpid(void); Returns: PRocess ID of calling processpid_t getppid(void); Returns: parent process ID of calling processuid_t getuid(void); Returns: real user ID of calling processuid_t geteuid(void); Returns: effective user ID of calling processgid_t getgid(void); Returns: real group ID of calling processgid_t getegid(void); Returns: effective group ID of calling process
  • 注意:这些函数都没有出错返回。
2. 函数fork
  • fork函数被调用一次,返回两次。子进程中返回值是0,父进程中返回值是子进程的pid
  • 子进程是父进程的副本,子进程获得父进程的数据空间、堆和栈的副本。注意,在是子进程拥有的副本。父子进程并不共享这些存储空间部分。父子进程共享正文段。
  • 由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、堆和栈的完全副本。作为替代,使用了写时复制技术。
  • 4种平台都支持的变体:vfork;linux的变体:clone系统调用,允许调用者控制哪些部分由父子进程共享。
  • fork之后是父进程先执行还是子进程先执行是不确定的
  • 父进程中的所有打开文件描述符都被复制到子进程中,父子进程为每个相同的打开描述符共享一个文件表项,故共享同一文件偏移量。如果父子进程写同一描述符执行的文件,又没有任何形式的同步,那么它们的输出就会混合。
  • 在fork之后处理文件描述符有以下两种常见的情况:
    1. 父进程等待子进程完成。这种情况下,父进程无需对其描述符做任何处理。
    2. 父进程和子进程各自执行不同的程序段。这种情况下,fork之后,父子进程各自它们不需要使用的文件描述符。
  • strlen和sizeof的区别:前者不包括null字节,一次函数调用;后者包括null字节,编译时计算

  • 除了文件描述符之外,父进程的很多其他属性也由子进程继承,包括:

    1. 实际用户ID、实际组ID、有效用户ID、有效组ID
    2. 附属组ID
    3. 进程组ID
    4. 会话ID
    5. 控制终端
    6. SUID和SGID标志(stat结构的st_mode成员)
    7. 当前工作目录
    8. 根目录
    9. 文件模式创建屏蔽字umask
    10. 信号屏蔽和处理
    11. 对任一打开文件描述符的执行时关闭(close-on-exec)标志
    12. 环境
    13. 连接的共享存储段
    14. 存储映像
    15. 资源限制
    16. 是否继承nice值由具体实现自行决定
  • 父进程和子进程之间的区别具体如下:

    1. fork的返回值不同
    2. pid不同
    3. 这两个进程的父进程不同
    4. 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0
    5. 子进程不继承父进程设置的文件锁
    6. 子进程的未处理闹钟被清除
    7. 子进程的未处理信号集设置为空集
  • fork失败的两个主要原因:

    1. 系统中已经有了太多的进程
    2. 该实际用户ID的进程总数超过了系统限制
  • fork有以下两种用法:

    1. 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务器中是常见的。
    2. 一个进程要执行一个不同的程序。这对shell是常见的情况。某些系统将fork+exec组合成一个操作spawn
3. 函数vfork
  • vfork函数的调用序列和返回值与fork相同,但两者的语义不同:
    1. vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序,故不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会引用该地址空间。不管在子进程调用exec或exit之前,它在父进程的空间中运行。
    2. 另一个区别是vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。故如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
4. 函数exit
  • 5种正常终止方式:
    1. 从main中执行return,等效于调用exit
    2. 调用exit函数,调用各终止处理程序,关闭标准I/O流,最后调用_exit函数
    3. 调用_exit或_Exit
    4. 进程的最后一个线程在其启动例程执行return语句,该进程以终止状态0返回
    5. 进程的最后一个线程调用pthread_exit,进程终止状态总是0
  • 3种异常终止方式:
    1. 调用abort,它产生SIGABRT信号
    2. 当进程接收到某些信号时,信号可由进程自身(如调用abort函数)、其他进程或内核产生
    3. 最后一个线程对“取消”请求做出响应
  • 不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应的进程关闭所有打开描述符,释放它所使用的存储器等。
  • 注意:“退出状态”(3个exit函数的参数或main的返回值)区别于“终止状态”。在最后调用_exit时,内核将退出状态转换为终止状态。

  • 如果父进程在子进程之前终止,则称子进程为孤儿进程。子进程 ppid变为1,称这些进程由init进程收养。一个init进程收养的进程终止时,init会调用一个wait函数取得其终止状态,防止它成为僵尸进程。

  • 如果子进程在父进程之前终止,内核为每个终止子进程保存了一定量的信息,至少包括pid、该进程的终止状态以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。在Unix术语中,一个已经终止、但其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵尸进程zombie/defunct。
5. 函数wait、waitpid
  • 当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。子进程终止是异步事件。
#include <sys/wait.h>pid_t wait(int *statloc);pid_t waitpid(pid_t pid, int *statloc, int options); Both return: process ID if OK, 0 (see later), or −1 on error
  • 调用wait或waitpid的进程可能:
    1. 如果其所有子进程都还在运行,则阻塞
    2. 如果一个子进程终止,正等待其父进程获取其终止状态,则取得该子进程的终止状态立即返回
    3. 如果它没有任何子进程,则立即出错返回
  • 如果进程由于收到SIGCHLD信号而调用wait,我们期望wait会立即返回。
  • wait与waitpid的区别
    1. waitpid有一选项,可使调用者不阻塞
    2. waitpid可以控制它所等待的进程
  • 若statloc不是NULL,则终止进程的终止状态就存放在它所指向的单元内。该整型状态字由实现定义,其中某些位表示退出状态(正常返回),其他位则指示信号编号(异常返回),有一位指示是否产生了core文件。

  • waitpid函数中的pid参数的解释:

    pid == -1,等待任一子进程,等价于wait函数pid > 0,等待pid等于该值的子进程pid == 0,等待组ID等于调用进程组ID的任一子进程pid < 0,等待组ID等于pid绝对值的任一子进程

  • waitpid函数中的options参数:WNOHANG(不阻塞)、WCONTINUED、WUNTRACED

  • 如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵尸状态直到父进程终止,实现这一要求的诀窍是调用fork两次。

#include "apue.h"#include <sys/wait.h>int main(void){ pid_t pid; if ((pid = fork()) < 0) {        err_sys("fork error"); } else if (pid == 0) { /* first child */ if ((pid = fork()) < 0)            err_sys("fork error"); else if (pid > 0)            exit(0); /* parent from second fork == first child *//** We’re the second child; our parent becomes init as soon* as our real parent calls exit() in the statement above.* Here’s where we’d continue executing, knowing that when* we’re done, init will reap our status.*/    sleep(2);    printf("second child, parent pid = %ld/n", (long)getppid());    exit(0);

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表