bash 启动程序的流程

文章目录

  • [1. 启动流程概要](#1. 启动流程概要)
  • [2. 实践](#2. 实践)
    • [2.1 新进程的 Process Group 观察](#2.1 新进程的 Process Group 观察)
    • [2.2 在 bash 启动的程序里创建新 session](#2.2 在 bash 启动的程序里创建新 session)
  • [3. 番外](#3. 番外)

1. 启动流程概要

bash 启动程序的 3 个主要步骤:fork() + setpgid() + exec*():

为什么 bash 要对新进程调用 setpgid()、将其放入一个独立的 Process Group 呢?主要原因如下:

  • bash job 控制。如果不将新程序放入独立的 Process Group,则新程序进程将继承父进程 bash 的 Process Group,这样 fg/bg/jobs 程序将无法 Process Group 唯一标识来控制新程序(这些程序按 PGID 进行操作);

  • 独立处理信号。如果 bash 不将新启动的程序放入独立 Process Group,则所有启动的进程都将位于和 bash 同一 Process Group 内。发给某个 (bash 启动的)程序的 Ctrl+C 等信号,将会传递给 bash 和其启动所有程序所在的同一 Process Group,从而使 bash 及其启动的程序都终止(自身配置了信号处理的程序除外);

  • bash 管道处理。像 cmd1 | cmd2 | cmd3 管道命令,如果不放入独立 Process Group,则无法当都当作一个 job 来控制处理;相反,则可以通过独立的 PGID 来 job 控制。

2. 实践

2.1 新进程的 Process Group 观察

bash 复制代码
$ cat t.c
#include <unistd.h>

void main(void)
{
	while (1) sleep(8);
}
$ ./t &
[1] 4612
$ ps -eo pid,ppid,pgid,sid,comm
    PID    PPID    PGID     SID COMMAND
      1       0       1       1 systemd
[......]
   4598    4580    4598    4598 bash
   4612    4598    4612    4598 t
[......]

从上面的 ps 命令输出信息看到:

  • 程序 t 的 PPID == 4598,也即 bash 的 PID,所以 t 是由 bash 启动的;
  • 程序 t 的 PID == PGID == 4612,所以 t 位于独立的 Process Group 4612 中;

2.2 在 bash 启动的程序里创建新 session

Linux 通过 setsid() 新建一个 Session。下面的 setsid_test 程序,试图通过 setsid() 新建立一个 session:

c 复制代码
#include <unistd.h>
#include <stdio.h>

int main(void)
{
	pid_t sid;

	sid = setsid();
	if (sid == -1) {
		perror("setsid");
		return -1;
	}
	printf("new session: %d\n", sid);

	while (1) sleep(8);

	return 0;
}

编译,然后从 bash 终端启动:

bash 复制代码
$ ./setsid_test
setsid: Operation not permitted

结果失败了,报 EPERM 错误。glibc 的 setsid() 是对系统接口 sys_setsid() 的直接调用,看下 sys_setsid() 的内核代码:

c 复制代码
SYSCALL_DEFINE0(setsid)
{
	struct task_struct *group_leader = current->group_leader; // group_leader == current,指向 setsid_test
	struct pid *sid = task_pid(group_leader);
	pid_t session = pid_vnr(sid);
	int err = -EPERM;
	
	write_lock_irq(&tasklist_lock);
	/* Fail if I am already a session leader */
	if (group_leader->signal->leader)
		goto out;

	/* Fail if a process group id already exists that equals the
	 * proposed session id.
	 */
	/* 如果进程已经是 Process Group Leader,则返回错误码 EPERM */
	if (pid_task(sid, PIDTYPE_PGID))
		goto out; /* (1) */

	/* 进程成为新的 Process Group/Session Leader */
	group_leader->signal->leader = 1;
	set_special_pids(sid);

	/* 断开与 tty 终端的连接 */
	proc_clear_tty(group_leader);

	err = session;
out:
	write_unlock_irq(&tasklist_lock);
	if (err > 0) {
		proc_sid_connector(group_leader);
		sched_autogroup_create_attach(group_leader);
	}
	return err;
}

测试程序 setsid_test 的 setsid() 调用在上面代码标注 (1) 处返回,表示进程已经是一个Process Group Leader,而 Linux 规定已经是 Process Group Leader 不能成为新的 Session Leader。如何解决这个问题?方案一是通过 fork() 调用:fork() 后,首先退出父进程,以摆脱 bash 通过 setpgid() 设置的 leader 身份;然后再在子进程中调用 setsid() 将会成功,因为子进程不具备 leader 身份,这正是 glibc 库函数 daemon() 采用的方式;另一个解决方案是不通过 bash 启动程序,而是通过 /etc/rc.local 或 SystemV 的 init script 启动程序。

那为什么不允许 Process Group Leader 成为 Session Leader ?因为一旦允许, 由于 setsid() 新成立了 Session 和 Process Group,所以 Process Group Leader 将进入新的 Process Group 和 Session,这样在原本 Process Group 内进程将失去它们的 Process Group Leader, 这样会引发歧义,同时使得像 bash 的 job control 也将无法工作。如果你无法理解这些逻辑,就简单的理解为这是 Linux 内核的强制规定就可以了。

我们使用 glibc 的 daemon() 接口来创建 Session,daemon() 通过 fork() 之后的子进程调用 setsid(),而 父进程则退出以摆脱 bash 赋予的 Process Group Leader 角色。修改后的代码如下:

c 复制代码
#include <unistd.h>
#include <stdio.h>

int main(void)
{
	pid_t sid;

#if 1
	if (daemon(0, 0) != 0) {
		perror("daemon");
		return -1;
	}
	
	sid = getsid(0);
#else
	sid = setsid();
	if (sid == -1) {
		perror("setsid");
		return -1;
	}
#endif

	printf("new session: %d\n", sid);

	while (1) sleep(8);

	return 0;
}
bash 复制代码
$ ./setsid_test
[2] 4864

$ ps -eo pid,ppid,pgid,sid,comm
    PID    PPID    PGID     SID COMMAND
      1       0       1       1 systemd
[......]
   2173       1    2173    2173 systemd
[......]
   4598    4580    4598    4598 bash
[......]
   4865    2173    4865    4865 setsid_test
[......]

首先观察到,setsid_test 的 printf() 输出的内容并没有看到,因为 setsid() 会断开与 tty 终端的连接;然后从 ps 命令输出也观察到,setsid_test 的 PID == PGID == SID == 4865,且 PPID == 2173,指向 systemd,而不是 bash,因为 fork() 的父进程退出了,保留了子进程,被托孤给了 systemd。

3. 番外

笔者在内核代码进程创建、程序加载执行、进程 SID 设置处加入一些,可以帮助理解本文内容(修改源码就不贴了):

bash 复制代码
[    2.769850] sys_setsid()-2#: current@ffff800035330000,systemd,1,1 group_leader@ffff800035330000,systemd, pgrp@          (null),(null), session@          (null),(null)
[    5.475602] sys_setsid()-2#: current@ffff800034afab80,(journald),1357,1357 group_leader@ffff800034afab80,(journald), pgrp@          (null),(null), session@          (null),(null)
[    5.533479] sys_setsid()-2#: current@ffff800034afba00,(modprobe),1358,1358 group_leader@ffff800034afba00,(modprobe), pgrp@          (null),(null), session@          (null),(null)
[    5.605649] sys_setsid()-2#: current@ffff800034afc880,(modprobe),1359,1359 group_leader@ffff800034afc880,(modprobe), pgrp@          (null),(null), session@          (null),(null)
[    5.668971] sys_setsid()-2#: current@ffff800034afd700,(modprobe),1360,1360 group_leader@ffff800034afd700,(modprobe), pgrp@          (null),(null), session@          (null),(null)
[    5.708364] sys_setsid()-2#: current@ffff800034488000,(modprobe),1361,1361 group_leader@ffff800034488000,(modprobe), pgrp@          (null),(null), session@          (null),(null)
[    5.763098] sys_setsid()-2#: current@ffff800034488e80,(les-load),1362,1362 group_leader@ffff800034488e80,(les-load), pgrp@          (null),(null), session@          (null),(null)
[    5.808880] sys_setsid()-2#: current@ffff800034489d00,(mount-fs),1363,1363 group_leader@ffff800034489d00,(mount-fs), pgrp@          (null),(null), session@          (null),(null)
[    6.372598] sys_setsid()-2#: current@ffff80003448ba00,(dom-seed),1366,1366 group_leader@ffff80003448ba00,(dom-seed), pgrp@          (null),(null), session@          (null),(null)
[    6.508326] sys_setsid()-2#: current@ffff80003448c880,(d-sysctl),1367,1367 group_leader@ffff80003448c880,(d-sysctl), pgrp@          (null),(null), session@          (null),(null)
[    6.695839] sys_setsid()-2#: current@ffff800034afe580,(sysusers),1368,1368 group_leader@ffff800034afe580,(sysusers), pgrp@          (null),(null), session@          (null),(null)
[    7.067327] sys_setsid()-2#: current@ffff80003448d700,(urnalctl),1371,1371 group_leader@ffff80003448d700,(urnalctl), pgrp@          (null),(null), session@          (null),(null)
[    7.285510] sys_setsid()-2#: current@ffff80003448e580,(tmpfiles),1372,1372 group_leader@ffff80003448e580,(tmpfiles), pgrp@          (null),(null), session@          (null),(null)
[    7.788486] sys_setsid()-2#: current@ffff800034448000,(tmpfiles),1374,1374 group_leader@ffff800034448000,(tmpfiles), pgrp@          (null),(null), session@          (null),(null)
[    8.395642] sys_setsid()-2#: current@ffff800034afd700,(resolved),1375,1375 group_leader@ffff800034afd700,(resolved), pgrp@          (null),(null), session@          (null),(null)
[    8.460479] sys_setsid()-2#: current@ffff800034afc880,(imesyncd),1376,1376 group_leader@ffff800034afc880,(imesyncd), pgrp@          (null),(null), session@          (null),(null)
[    8.508925] sys_setsid()-2#: current@ffff800034afba00,(ate-utmp),1377,1377 group_leader@ffff800034afba00,(ate-utmp), pgrp@          (null),(null), session@          (null),(null)
[   10.588649] sys_setsid()-2#: current@ffff800034afba00,(s-daemon),1379,1379 group_leader@ffff800034afba00,(s-daemon), pgrp@          (null),(null), session@          (null),(null)
[   10.731695] sys_setsid()-2#: current@ffff800034afe580,(crub_all),1380,1380 group_leader@ffff800034afe580,(crub_all), pgrp@          (null),(null), session@          (null),(null)
[   10.868722] sys_setsid()-2#: current@ffff800034af9d00,(spatcher),1383,1383 group_leader@ffff800034af9d00,(spatcher), pgrp@          (null),(null), session@          (null),(null)
[   11.041570] sys_setsid()-2#: current@ffff800034af8e80,(d-logind),1384,1384 group_leader@ffff800034af8e80,(d-logind), pgrp@          (null),(null), session@          (null),(null)
[   11.139285] sys_setsid()-2#: current@ffff800034af8000,(sessions),1385,1385 group_leader@ffff800034af8000,(sessions), pgrp@          (null),(null), session@          (null),(null)
[   16.689700] sys_setsid()-2#: current@ffff800034449d00,(agetty),1387,1387 group_leader@ffff800034449d00,(agetty), pgrp@          (null),(null), session@          (null),(null)
[   16.694360] sys_setsid()-2#: current@ffff800034448e80,(agetty),1386,1386 group_leader@ffff800034448e80,(agetty), pgrp@          (null),(null), session@          (null),(null)
[   95.171292] sys_setsid()-2#: current@ffff800034af8000,(ate-utmp),1402,1402 group_leader@ffff800034af8000,(ate-utmp), pgrp@          (null),(null), session@          (null),(null)
[  180.544365] sys_setsid()-2#: current@ffff80003444c880,(time-dir),1412,1412 group_leader@ffff80003444c880,(time-dir), pgrp@          (null),(null), session@          (null),(null)
[  180.821462] sys_setsid()-2#: current@ffff80003444d700,(systemd),1413,1413 group_leader@ffff80003444d700,(systemd), pgrp@          (null),(null), session@          (null),(null)
[  181.911442] SYSC_setpgid(): p@ffff8000321be580,bash,1419,1419 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 pgrp@ffff8000320f4600
[  181.959156] copy_process(): init_struct_pid@ffff200009b56520
[  181.959307] copy_process()-1#: p@ffff800031610000,bash,1420,1420 p->group_leader@ffff800031610000,bash,1420,1420 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 task_pgrp(current)=ffff8000320f4600, task_session(current)=ffff800033f1f480
[  181.972394] SYSC_execve(): /usr/bin/groups current@ffff800031610000,bash,1420,1420
[  182.028751] copy_process()-1#: p@ffff800031610e80,bash,1421,1421 p->group_leader@ffff800031610e80,bash,1421,1421 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 task_pgrp(current)=ffff8000320f4600, task_session(current)=ffff800033f1f480
[  182.080254] copy_process()-1#: p@ffff800031611d00,bash,1422,1422 p->group_leader@ffff800031611d00,bash,1422,1422 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 task_pgrp(current)=ffff8000320f4600, task_session(current)=ffff800033f1f480
[  182.134983] copy_process()-1#: p@ffff800031612b80,bash,1423,1423 p->group_leader@ffff800031612b80,bash,1423,1423 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 task_pgrp(current)=ffff8000320f4600, task_session(current)=ffff800033f1f480

[  192.925429] copy_process()-1#: p@ffff800031613a00,bash,1424,1424 p->group_leader@ffff800031613a00,bash,1424,1424 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 task_pgrp(current)=ffff8000320f4600, task_session(current)=ffff800033f1f480
[  192.928675] SYSC_setpgid(): p@ffff800031613a00,bash,1424,1424 current@ffff800031613a00,bash,1424,1424 current_group@ffff800031613a00,bash,1424,1424 pgrp@ffff80002fc70200
[  192.939927] SYSC_execve(): ./setsid_test current@ffff800031613a00,bash,1424,1424
[  192.965327] sys_setsid()-1#: current@ffff800031613a00,setsid_test,1424,1424 group_leader@ffff800031613a00,setsid_test,1424,1424 pgrp@ffff800031613a00,setsid_test,1424,1424 session@          (null),(null)

[  213.538658] copy_process()-1#: p@ffff800031614880,bash,1425,1425 p->group_leader@ffff800031614880,bash,1425,1425 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 task_pgrp(current)=ffff8000320f4600, task_session(current)=ffff800033f1f480
[  213.539338] SYSC_setpgid(): p@ffff800031614880,bash,1425,1425 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 pgrp@ffff80002fc70280
[  213.546247] copy_process()-1#: p@ffff800031418000,bash,1426,1426 p->group_leader@ffff800031418000,bash,1426,1426 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 task_pgrp(current)=ffff8000320f4600, task_session(current)=ffff800033f1f480
[  213.546731] SYSC_setpgid(): p@ffff800031418000,bash,1426,1426 current@ffff8000321be580,bash,1419,1419 current_group@ffff8000321be580,bash,1419,1419 pgrp@ffff80002fc70280
[  213.558916] SYSC_execve(): /usr/bin/dmesg current@ffff800031614880,bash,1425,1425
相关推荐
wuminyu2 分钟前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
EVEN-09137 分钟前
Linux-线程控制
linux·jvm
刚入门的大一新生15 分钟前
Linux-线程控制
linux·运维·服务器
C++ 老炮儿的技术栈29 分钟前
Linux 动态库踩坑:明明重新编译了 .so,调用函数返回值却一直不变(libfjrobot.so 版本号异常排查实录)
linux·c语言·网络·c++·visual studio
你看我名字2 小时前
云计算工程师技能树:从 Linux 基础到云平台架构能力
linux·架构·云计算
qq_4017004110 小时前
Linux设备树详解
linux·运维·服务器
云梦谭11 小时前
让 Linux 主动连接 Windows:配置 Windows 11 SSH 服务端
linux·windows·ssh
刚入门的大一新生11 小时前
Linux-信号
linux·运维·服务器
GeW15 小时前
新质生产力路线图解读:数据库性能优化与Linux调优专场
linux
mounter62516 小时前
深入理解 gccrs:GCC 如何实现 Rust 编译器,以及编译 Linux 内核面临的挑战
linux·驱动开发·rust