linux系统编程——进程管理——基础

1. 前言

进程只运行的程序,由汇编语言,数据,资源,状态,虚拟计算机组成。
unix将运行程序分为 创建进程fork,加载二进制exec

1. exec

将二进制程序加载到内存,并开始新程序的执行。

一次成功的exec 会对进程有如下改变:

  • 改变地址空间和进程映像
  • 任何未决信号丢失
  • 进程捕捉信号回到默认动作,因为信号处理程序已经消失在进程地址空间。
  • 内存任何锁定丢弃
  • 多数线程属性回到默认值
  • 多数进程统计数据重置
  • 与进程内存有关的任何东西,包括映射的文件,会被丢弃
  • 单独存在于用户空间的任何东西,包括C链接库的功能,例如atexit()的行为,会被丢弃

而进程许多特性不会改变:
进程ID,父进程ID,优先级,所有者所有组。

打开的文件可以跨exec被继承,意味新程序可以访问原进程打开的所有文件,假设其直到相应文件描述符的值。
若不期望这行为,可以在exec前先关闭文件,也可以通过fcntl指示内核自动完成。

2. fork

创建新进程来运行与当前相同的映像

父子进程几乎完全一样,除了

  • 子进程pid
  • 子进程ppid
  • 子进程资源统计数重置为0
  • 任何未决信号被清除
  • 所取得任何文件锁定不会被子进程继承

2.1 写时复制

fork时,linux不会复制父进程地址空间,而是写时才复制页面。
由于写时复制,若立即执行exec,便不会浪费复制。

2.2 vfork

vfork是写时复制之前出现的,以解决立即执行exec动作,导致fork期间浪费地址空间的复制。
vfrok通过暂停父进程,直到子进程终止或执行exec。
在过渡期间父子进程通向地址空间和页表项,因此子进程不得修改地址空间中任何内存的内容。

由于有了写时复制,且vfork有bug,所以不要使用

3. exit

终止进程。
EXIT_SUCCESS EXIT_FAILURE 被定义为可移植的成功和失败。

在终止进程前,c链接库会依次执行下面的shutdown步骤

  • 调用atexit 注册的任何函数
  • 刷新已打开的标准IO流
  • 移除任何由tmpfile() 创建的临时文件。

4. wait

unix设计决定,若子进程先于父进程死亡,则子进程向父进程发信号SIGCHILD,且内核让子进程进入特殊状态,僵尸进程。
进程只会保留最小骨架,等待父进程打听它的状态。
只有在父进程获得终止子进程的信息后,子进程才正式结束。

使用wait获得已终止子进程的信息。

5. system

生成一个进程并等待其终止,用于运行shell script。
通常目的是获得命令返回值。
执行成功时,返回值就是命令的返回状态,如同wait所提供的状态,因此可用 WEXITSTATUS 获得命令的结束码。
命令执行期间 SIGCHILD 被阻挡,SIGINT SIGQUIT被忽略。
由于SIGINT SIGQUIT被忽略,所以你可能需要检查子进程的结束状态

ret = system("ls -l");
if (WIFSIGALED(ret) &&
    (WTERMSIG(ret) == SIGINT ||
     WTERMSIG(ret) == SIGQUIT))
      exit(0);

也可以实现不忽略信号的system

int my_system(const char *cmd)
{
  int status;
  pid_t pid;

  pid = fork();
  if (pid == -1)
    return -1;
  else if (pid == 0) {
    const char *argv[4];
    argv[0] = "sh";
    argv[1] = "-c";
    argv[2] = cmd;
    argv[3] = NULL;
    execv("/bin/sh", argv);

    exit(-1);
  }
  if (waitpid(pid, &status, 0) == -1)
    return -1;
  else if (WIFEXITED(status))
    return WEXITSTATUS(status);
  return -1;
}

posted on 2021-08-24 07:34  开心种树  阅读(54)  评论(0编辑  收藏  举报