APUE.03a::进程

创建进程

fork

  • 返回0: 子进程
  • 返回>0: 父进程, 返回值是子进程pid
  • 子进程会得到父进程的堆(IO缓存, malloc的内存)、栈(局部变量)、数据空间(Data Segment)的拷贝, 在子进程里修改这些变量并不会影响父进程中的值, 注意这种拷贝是“写时复制”(Copy On Write,COW);
  • fork前打开的文件句柄, 其偏移量会在父子进程间共享, 原因是进程内存中仅保存了文件句柄的fd指针, 指针指向的结构体(也就是文件表,保存了文件标准和位移)是共享的. @doubt 那么”文件表”是存储在哪里的?
  • 另外需要注意的是, 因为堆内存也将被拷贝(IO缓存在堆里), 所以如果在创建子进程之前这个IO缓存中就有数据, 那么也会带入子进程, 导致子进程的IO缓存里”多”出一些数据.
int main()
{
// child process because return value zero
if (fork() == 0) {
printf("Hello from Child!\n");
}
// parent process because return value non-zero.
else {
printf("Hello from Parent!\n");
}
return 0;
}

vfork

  • 返回值同 fork
  • 不同点1: vfork创建的子进程与父进程共享数据段, 在子进程中修改变量也会影响到父进程中的变量
  • 不同点2: vfork的子进程优先于父进程执行, 当子进程明确_exit() 或者exit()之后, 父进程才会继续执行.

clone

clone可以看成是fork的升级版, 不仅可以创建进程或者线程, 还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

终止进程

  • 正常三种: return 语句, exit() 或 _exit(),
  • 非正常: abort(), 调用该函数之后, 调用者会收到 SIGABRT

wait/waitpid

  • pit_t wait(int *status): 父进程调用后立刻阻塞, 直到第一个子进程结束, 子进程结束后系统会发送SGICHLD信号, 收到这个信号后, 父进程从wait返回
  • pid_t waitpid(pid_t pid, siginfo_t *infop, int options), 等待指定的进程

exec

fork 或者 vfork之后往往需要再调用 exec启动另一个新程序, 因为 exec不创建新进程, 所以pid不会变, 原程序的 Text Seg, Data Seg, Heap/Stack会被替换

僵尸进程和孤儿进程

  • 僵尸进程(zombie process): ps显示stat为”z”的进程

    • 产生原因: 子进程退出后(exit, 或发生错误), 子进程仍存在于进程表, 当父进程调用wait之后才会从进程表删除. 如果子进程死掉但是父进程没有调用wait, 子进程就变成了僵尸进程;
    • 正确做法: 子进程死后, 系统会向父进程发生SIGCHLD信号, 父进程收到此信号后应该用wait处理子进程;
    • 如果父进程没有处理SIGCHLD信号, 那么只能kill父进程, 让init成为子进程的父进程, init进程会周期性调用wait清理Zombie进程.
    • 处理SIGCHLD信号示例代码: https://docs.oracle.com/cd/E19455-01/806-4750/signals-7/index.html
  • 孤儿进程(orphan process): 父进程死掉, 子进程被init进程接管

  • 守护(Daemon)进程: 守护进程就是后台服务进程, 因为它会有一个很长的生命周期提供服务, 关闭终端不会影响服务, 也就是说可以忽略某些信号
  • 如何实现Deamon进程:
    • 父进程exit
    • command &
    • nohup command

守护进程(daemon)

Linux 守护进程原理及实例(Redis、Nginx) - CSDN博客

  • 守护进程不属于任何一个控制终端, 不属于任何一个会话(Session)
  • 守护进程没的父进程是0 @doubt
  • 守护进程会忽略一些signal(包括处理信号SIGHUP(进程和控制终端分离时收到SIGHUP)、 SIGTERM(系统关机之前收到SIGTERM)

Namespace

@ref:

对于Namespace的操作有以下方式:

  1. 可以在进程刚创建的时候通过clone系统调用为新进程分配一个或多个新的Namespace。
  2. 通过setns()将进程加入到已有的Namespace中。
  3. 通过unshare()为已存在的进程创建一个或多个新的Namespace。