进程创建之:fork()
#include <unistd.h>
pid_t fork(void);
// 在父进程中,返回值是子进程的id
/// 在子进程中,返回值是0
// 返回-1表示发生了错误
如何理解fork呢? fork之后,将存在两个进程,并且都从fork的下一个语句开始执行。 两个进程拥有值相等但是空间独立的内存(堆、栈等)
要注意的是fork之后的子进程和父进程会共享同一个文件描述符(就是文件句柄 file open()),并且文件描述符的偏移也是共享的,换句话说,父子进程可以同时对文件进行写,不会互相覆盖。但是读的话就需要保证另一个进程执行完毕,否则读到的值不完整。这里可以通过进程间通信的手段去维护。
进程的几种状态
Z (TASK_DEAD - EXIT_ZOMBIE) 僵尸进程
父进程创建了子进程,父进程没有调用wait(),那么内核中的进程表就为子进程永久保留一条记录task_struct。task_struct里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。比如在shell中,$?变量就保存了最后一个退出的前台进程的退出码,而这个退出码往往被作为if语句的判断条件。
wait()系统调用:进程阻塞,直到子进程执行完毕,并且回收内核中进程表中子进程信息,再返回。
杀死僵尸进程的几种方法:
1)改写父进程,在子进程死后,为其收尸
子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行 waitpid()函数为子进程收尸。
2)杀死父进程
父进程死后,子进程会变成“孤儿进程”,把孤儿进程过继给init,init会把它清理
3)重启。
R (TASK_RUNNING),可执行状态
操作系统教材中把进程分为read 和running 状态,linux中用这一个状态来合并。
S (TASK_INTERRUPTIBLE),可中断的睡眠状态
等待某某事件而阻塞,例如上面的wait,socket连接。 对于操作系统中的挂起。
可中断意味着可以接受信号,例如kill pid
D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
很明显,kill pid 无效。主要用于内核,下面这段代码也可以引起D进程
#include void main() { if (!vfork()) sleep(100); }
T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
进程收到SIGSTOP信号,就会响应信号而进入TASK_STOPPED ,除非本身处于D。 再收到一个SIGCONT信号,就能恢复到TASK_RUNNING状态。
TASK_TRACED进程被跟踪时的状态, 它会停下来,等待跟踪它的进程对其操作。