**Linux 知识补充 -- 守护进程的编写及使用方法 - 01 正常程序的后台运行及前后台切换** 1652270 **冯舜** ## 建立子目录 如//1dir//, 建立所有的子目录. !![1dir,建立所有子目录] ## 编写死循环程序 如//1edit//, 使用 vim 编写两个死循环程序和它们的 `makefile`; 编写后的内容如//1content//. !![1edit,编辑三个文件] !![1content,三个文件的内容] ## 编译并运行, 放入后台运行 如//1makerun//, 执行 `make` 之后运行 `./test1-1`, 运行第一个死循环程序. !![1makerun,构建并运行 `test1-1`] 要将前台进程挂起到后台, 需要用 `Ctrl-Z` 发送 `^Z` 字符到进程. 之后如果想要使它继续后台运行, 使得用户可以继续输入命令, 可以运行 `bg %1` (数字取决于 `Ctrl-Z` 后输出的作业号, 也可以使用 `jobs` 查询) 或直接运行 `bg` (默认后台运行最近一个挂起的进程). 此时进程继续后台运行, 虽然会向屏幕输出字符产生干扰, 但用户输入回车键后会产生命令提示符, 接受用户输入命令. 如//1ctrlz//. !![1ctrlz,后台运行 `test1-1`] ## 查看在后台的 `test1-1` 程序的信息 `test1-1` 后台挂起或后台运行时, 可以使用 `jobs -l` 查询其作业号 (1) 和 PID (63580). 之后可以运行 `ps -p PID号` (可加 `-u` 或 `-l` 使获得的信息更详细) 获得该进程的有关信息. 如//1procinfo//. !![1procinfo,查询在后台的程序信息] ## 使 `test1-1` 切换到前台 执行 `fg %1` (数字取决于作业号, 另外可以不加参数或使用 `%+` 或 `%%` 选择最近挂起的作业号, 以及 `%-` 选择第二近挂起的作业号) 将 1 号作业切换到前台. 此时命令提示符不会应用户的回车而出现, 用户也无法再运行命令行, 除非按 `Ctrl-C` 使用 `^C` 字符终止命令. 如//1fg//. !![1fg,作业 1 切换到前台] ## 后台运行 `test1-1` 和 `test1-2`, 控制某一个切换到前台 如//1test1-2//, 用 `^Z` 将两个进程都挂起到后台, 用 `jobs` 查看它们的作业号 (位于方括号内). 之后, 可以用 `fg %作业号` 将其中之一切换到前台. !![1test1-2,将一个作业切换到前台] ## 查看父进程标识号 如//1ppid//, 使用 `jobs -l` 查看每个作业的 PID 后, 使用 `ps -p PID号 -o ppid=` 查看其父进程的 PID (PPID). 也可以去掉 `-o ppid=` 参数, 使用 `-u`、`-l` 或 BSD 风格的选项 `u`、`l` (某些情况下, BSD 选项使用结果和前面的 UNIX 选项的效果略有不同, 如是否只展示当前用户的进程等) 查看更详细信息, 其中父进程 PID 在 PPID 栏中. !![1ppid,查看父进程的 PID] ## 在另一个控制台看到原控制台的进程信息 如//1anothercon//所示: !![1anothercon,另一控制台查询原控制台进程] 若记住原控制台进程的 PID, 可使用 `ps -p PID1,PID2,... (其他参数)` 查看它们的信息; 使用 `ps a` (BSD 风格选项, 只要被认为选用了 BSD 选项, 则默认可查看同用户不同终端下进程) 或 `ps f` (也是 BSD 风格选项, 可以树的形式查看父子进程关系) 或 `ps -e` (查看全部进程), 按需结合 `grep` 选项用以查找可执行文件名, 一般可找到原控制台的进程信息. 添加 `l`、`-f` 等选项看到更详细的信息, 包括在 PPID 栏中的父进程 PID. 若要只列出原控制台的进程: 首先在原控制台运行 `tty`, 获取原控制台的 TTY 设备名, 如//1tty//. 再在另一控制台下运行 `ps -t TTY设备名 (其他参数)`, 可看到原控制台下的进程. 如//1pstty//. !![1tty,查看原控制台 TTY 设备] !![1pstty,另一控制台查询原控制台进程(根据 TTY 设备)] 也可以在原控制台下使用 `echo $$` 获取原控制台 bash 的 PID (如//1ppid2//), 在另一控制台下用 `ps --ppid 原bash的PID (其他参数)` 列出原控制台 bash 的子进程, 如//1psppid//. !![1ppid2,查看原控制台 TTY 设备 bash PID] !![1psppid,另一控制台查询原控制台进程(根据 bash PID)] ## 登出后台运行的程序的父 bash 如图//1logout//, 在 `test1-1` 和 `test1-2` 正在后台运行的状态中, 按 `Ctrl-D` 登出终端. !!! (https://unix.stackexchange.com/questions/84737/in-which-cases-is-sighup-not-sent-to-a-job-when-you-log-out) 在 bash 终端下, `Ctrl-D` 和 `exit`、`logout` 的作用大致相同. `Ctrl-D` 字符表示输入流完毕 (EOF), 会让 bash 遵循正常流程退出 (若不多加设置忽略 EOF); 而 `exit` 和 `logout` 同样会使 bash 正常退出. 此时, 该 bash 启动的后台运行作业不会退出 (但前台作业、后台挂起作业会退出), 而是作为孤儿进程被 `init` (1 号进程) 接纳作为子进程. 但点击 SSH 窗口或 Linux 桌面环境下 Terminal 窗口的 "X" 键退出, 则会向 bash 送一个 `SIGHUP` 信号, bash 收到后, 所有受终端控制的进程 (即 bash 会话[session]下打开的进程) 都会收到 `SIGHUP` 信号 (除非该作业用 `disown -h %1` 排除出 `SIGHUP` 信号管理列表或 (猜测) 使用 `setsid` 摆脱当前终端的控制). 收到 `SIGHUP` 信号的进程, 默认终止. 故包括后台运行进程在内的作业都会被终止. !![1logout,登出终端] 此时在另一终端用 `ps -f -C 可执行文件名` 查找这两个进程的信息, 发现它们并没有退出, 只是父进程标识号(PPID) 变为了 1, 如//1changeppid//. !![1changeppid,PPID 变更为 1] ## 使父终端退出登录的 `test1-1` 和 `test1-2` 在另一终端继续运行并打印信息 使用 `ps l -C test1-1` 和 `ps l -C test1-2` 查看两个进程的信息如//1checkS//, 发现它们的状态 `STAT` 为 `S` (等待状态) 而非 `T` (停止状态), 说明它们的确已经在继续运行. 只是因为 `TTY`(控制终端) 为空 `?`, 它们没有办法向某个 TTY 打印输出, 也无法受到某个 TTY 的控制. !![1checkS,查看进程信息] 若要使它们能在另一终端继续打印信息, 需要额外的工具如 `gdb` 等. 现使用 `reptyr`. `reptyr` 是一个能将进程的控制终端和 0、1、2 号文件描述符 (对应 `stdin`、`stdout`、`stderr`) 更改为当前终端的小工具. 从 https://centos.pkgs.org/7/epel-x86_64/reptyr-0.5-1.el7.x86_64.rpm.html 下载适合 CentOS 7 的 reptyr RPM 包, 传送到虚拟机上. 在虚拟机上使用 `yum install -y reptyr-0.5-1.el7.x86_64.rpm` 安装它, 如//1installreptyr//. !![1installreptyr, 安装 `reptyr`] 先确认要重新绑定终端的两个进程未被停止, 方法如上. 若已停止, 使用 `kill -CONT (PID号)` 继续它们的运行. 之后使用 `reptyr -s (PID号)` 重新绑定进程到当前终端, 如//1reptyr2//. 可以发现, 进程 `test1-1` 的输出现在来到了当前终端. 虽然更改了进程的控制终端, 但 `^Z` 字符无法停止它(可能是这个工具的缺陷), 只能使用 `^C` 字符强制终止. 从另一个终端中用 `ps l -C test1-1` 查看 `test1-1` 的信息, 如//1reattach//, 发现 `test1-1` 进程成功绑定到了新终端 `pts/4`. !![1reptyr2,重新绑定终端] !![1reattach,`ps` 中发现已经成功绑定新终端] ## 直接将正常程序放入后台 如//1bg2//, 在正常执行程序命令行的基础上, 尾部加上 `&` 即可将该程序放入后台执行. !![1bg2,直接放入后台]