进程相关fork()/exec()/wait()

fork()

fork()将父进程复制一份子进程, 在子进程中从fork()调用处继续执行, 之后的代码在父子进程中各自执行一遍. 最终父进程的fork()返回子进程的pid, 子进程的fork()返回0表示创建成功. 所以看起来仿佛fork()返回两个返回值, 其实是两个进程的fork()各自的返回值, 通过返回值不同区分父子进程.
getpid()获取当前进程pid, getppid()获取父进程pid.
getuid()获取当前进程实际用户, geteuid()获取当前进程有效用户. 如使用sudo命令时shell进程有效用户就变为root, 但实际用户还是username.

循环创建子进程如下所示:

当pid==0即在子进程中时要跳出循环, 避免创建"孙进程". 使得只有主进程有调用fork

父子进程各自的代码段是独立的, 各自的全局变量也是独立的, 但对于只读操作可以共享同一块物理内存, 写时再复制, 虚拟地址空间还是独立的.
父子进程谁先执行并不一定.
gdb使用set follow-fork-mode child/parent来跟踪父进程或子进程, 注意要在运行到 fork()前设置.

exec()函数族

调用exec函数会将当前进程的.text,.data段完全替换为新程序的.text和.data段, 但是不创建新进程, 所以进程id不变.

头文件<unistd.h>
extern char **environ;
原型:
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
execl()/execlp()/execle()

execl("/bin/ls", "ls", "-l", "-a", NULL)直接传入文件路径, 不需要PATH环境变量, 第二个参数起是命令行参数argv[0], argv[1]..., 命令行参数也是一个字符串数组, 结尾的哨兵NULL要显式的写出来.
execlp("ls", "ls", "-l", "-a", NULL) 该函数需要配合PATH来搜索需要加载的程序. 其第一个参数ls指的是要从PATH中查找的程序; 第二个参数ls指的是argv[0], 表示要被调用的程序. 此处文件中不需要引入环境变量extern char** environ, 函数会自动去查找. 其中environ是一个指针数组, 每一个指针指向一个char*字符串. execlp()通常用来调用系统程序如ls, date, cp, cat等命令.
先声明char* const envp[] = {"AA=11", "BB=22", NULL}; 然后将envp传入execle("/bin/ls", "ls", "-l", "-a", NULL, envp). 原进程会将环境变量信息传递给新进程, 可以利用execle函数传递自己需要的环境变量信息.

execv()/execvp()/execve()

先声明char* argv[]={"ls", "-l", "-a", NULL};char* const envp[] = {"AA=11", "BB=22", NULL}; 然后execv("/bin/ls", argv)直接声明一个命令行参数数组, 再传进去. 这里的v指代argv. 同理, execvp("ls", argv)需要借助PATH, execve("/bin/ls", argv, envp)需要传入环境变量.

若想将ps -aux输出到屏幕的内容输出到一个文件中, 可以使用dup2(fd, STDOUT_FILENO)将文件描述符表1号的STDOUT_FILENO指向对应文件的fd, 然后再execlp("ps", "ps", "aux", NULL).
注:1. 上述exec系列函数底层都是通过execve()系统调用实现, 2. exec函数族成功了不返回值, 失败了才返回errno, 所以后面的close(fd)实际上是不执行的, 不过依赖于隐式回收可以在进程结束时自动关闭文件, 所以不写close(fd)不会出错. 如果exec失败也不需要if判断, 直接perror接exit即可

孤儿进程

若父进程先于子进程结束, 则子进程成为孤儿进程, 其被/sbin/init进程领养(pid=1)或是/usr/sbin/init进程, 然后被回收.

僵尸进程

子进程死亡后父进程未处理, 则子进程成为僵尸进程, PCB仍存放在内核中. 使用ps aux查看时发现进程名变成[进程名]<defunct>, 表示是一个僵尸进程, 状态为Z. 另外运行中的进程状态是R, 后台运行的状态是S.
僵尸进程已经死亡, 无法用kill杀死, 所以只能回收. 回收一个僵尸进程可以调用wait()或者waitpid(), 也可以将其父进程杀死后使其变为孤儿进程, 由init领养后回收.

wait()

pid_t wait(int *status)传出参数status(配合宏)表示僵尸进程的成因, 返回值为僵尸进程pid. wait()函数可以清除PCB残留信息, 使父进程阻塞等待子进程完成. 一次wait()调用只能回收一个子进程.

// 当子进程正常结束时(如return 13或者 exit(13)), 可以获取到退出信息13
if (WIFEXITED(status))
{
    printf("child exit with %d", WEXITSTATUS(status));
}
// 当程序异常退出时(接收到信号), 获取中断信号(如9, kill -9)
// 当kill不加参数时信号默认为15-SIGTERM, 另外段错误是11, 可使用kill -l查看各种信号
if (WIFSIGNALED(status))
{
    printf("child killed by %d", WTERMSIG(status));
}
if (WIFSTOPPED(status)
{
    printf("child stopped by %d", WSTOPSIG(status));
}
if (WIFCONTINUED(status))
{
    printf("child continued");
}

waitpid()

pid_t waitpid(pid_t pid, int* status, in options);同wait, 但可以指定pid进程清理, 也可以不阻塞(options参数使用WNOHANG轮询模式), options为0则为阻塞.

fork的子进程默认跟父进程是一个进程组的, 所以如果父进程调用waitpid()时第一个参数传0和传-1是一样的. 父子进程组ID默认为父进程的ID
如果第一个参数传-xxxx就会把这一进程组的子进程都回收, 使用ps -ajx可以查看到进程组ID, 等价于kill -9 -xxxx(进程组ID).
当第三个参数为0时, waitpid(-1, NULL, 0)等价于wait(NULL), 回收任意子进程. 第三个参数传WNOHANG时(需使用轮询结构), 非阻塞回收, 如果子进程正在运行函数返回0, 如果成功清理返回子进程ID, 如果失败(无子进程)返回-1.

进程组/会话

getpgrp()获取当前进程的进程组ID, getpgid获取指定进程的进程组ID(传0就是自身的). setpgid使某一进程自立门户, 成为新进程组的组长.

会话的SID是创建该会话的领头进程的PID, 一般就是shell. Session的意义在于多个进程组(job)在一个终端中运行,其中的一个为前台 job,它直接接收该终端的输入并把结果输出到该终端。其它的 job 则在后台运行。
如果我们在 session 中执行了 nohup 等类似的命令,当 session 消亡时,相关的进程并不会随着 session 结束,原因是这些进程不再受 SIGHUP 信号的影响.
nohup java -jar app.jar >log 2>&1 &
最后一个&表示把条命令放到后台执行, 2>&1一定要写到>log后面,才表示标准错误输出和标准输出都重定向到log中.
本来1----->屏幕 (1指向屏幕)
执行>log后, 1----->log (1指向log)
执行2>&1后, 2----->1 (2指向1,而1指向log,因此2也指向了log)

守护进程

创建守护进程: 1. 创建子进程, fork() 2. 子进程创建新会话,丢弃终端, setsid() 3. 移动工作目录到根目录, chdir() 4. 改变umask掩码, umask(0002) 5. 重定向012文件描述符到/dev/null(会话不需要终端), dup2() 6. 将1-5封装到一个函数中来调用, 之后开始执行守护进程任务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容

  • 引出 我们学习了进程,是为了去用多进程,那么,为什么需要用到多进程呢? 1:为了提高效率,支持大用户量的并发。 2...
    小鼻子球球小昏昏阅读 288评论 0 0
  • Linux 进程管理与程序开发 进程是Linux事务管理的基本单元,所有的进程均拥有自己独立的处理环境和系统资源,...
    JamesPeng阅读 2,467评论 1 14
  • 使用 system C标准库中的system 函数提供了一种调用其他程序的简单方法,利用system 函数调用程序...
    一叶之界阅读 2,160评论 0 0
  • ### main函数执行之前做了什么?(iOS) & dyld 是Apple 的动态链接器;在 xnu 内核为程...
    天使君阅读 690评论 0 1
  • 基本概念 进程是一个可执行程序的实例,程序代码依托一个进程运行;进程由用户空间和一系列内核数据结构构成。内核数据段...
    loopppp阅读 495评论 0 0