进程间的关系 下
需要用一种方法知道哪一个进程组是前台进程,这样伪终端程序就知道将终端输入和终端产生的信号发送到何处。
#include <unistd.h>
pid_t tcgetpgrp(int fd);
int tcsetpgrp(int fd, pid_t pgrpid);
函数tcgetpgrp返回前台进程组的id,他和fd上打开终端相关联。
给出控制tty的文件描述符,通过tcgetsid函数,应用程序能获得首进程的进程组id。
#include <termios.h>
pid_t tcpgetsid(int fd);
需要管理控制终端的应用程序k恶意调用tcgetsid函数识别出控制终端首进程的会话id。
demo:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <termios.h>
#include <fcntl.h>
static void judge(void){
pid_t pid;
pid = tcgetpgrp(STDIN_FILENO);
if(pid == -1){
perror("tcgetpgrp");
return;
}else if(pid == getpgrp()){
printf("foreground\n");
}else{
printf("background\n");
}
}
int main(void){
printf("tcgetsid:%d,pgrp=%d,sid=%d\n",tcgetsid(STDIN_FILENO),getpgrp(),getsid(getpid()));
signal(SIGTTOU,SIG_IGN);
judge();
int result;
result = tcsetpgrp(STDIN_FILENO,getpgrp());
if(result == -1){
perror("tcsetpgrp");
return -1;
}
judge();
return 0;
}
9.8作业控制
作业控制是1980年新增的一个特征。他允许下在一个终端上启动多个作业(进程组)。它控制哪一个作业可以访问该终端以及哪些作业在后台运行。作业控制有以下三个形式的支持。
1)支持作业控制的shell
2)内核中的终端驱动必须支持作业控制。
3)内核必须提供某些作业控制的信号支持
一个作业是几个进程的合集,通常是一个进程管道。例如
vi main.c
前台进程和后台进程的切换
在Linux终端运行命令的时候,在命令末尾加上 & 符号,就可以让程序在后台运行
[root@Ubuntu$] java Main &
如果程序正在前台运行,可以使用 Ctrl+z 选项把程序暂停,然后用jobs -l查看刚才暂停的程序的number(工作号),然后使用bg %[number] 命令让这个暂停的程序在后台继续运行,jobs命令查看当前终端后台运行的任务,同时也能看到该任务的运行状态。
ps 和 jobs。区别在于 jobs 只能查看当前终端后台执行的任务,换了终端就看不见了。而ps命令适用于查看瞬时进程的动态,可以看到别的终端的任务。
对于所有运行的程序,我们可以用jobs –l 指令查看
[root@Ubuntu$] jobs -l
也可以用 fg %[number] 指令把一个程序调到前台,也就是把刚才后台运行的进程调到前台
二、fg、bg、jobs、&、nohup、ctrl+z、ctrl+c 命令
1、& 加在一个命令的最后,可以把这个命令放到后台执行,如 watch -n 10 sh test.sh & #每10s在后台执行一次test.sh脚本
2、ctrl + z 可以将一个正在前台执行的命令放到后台,并且处于暂停状态。 3、jobs 查看当前有多少在后台运行的命令 jobs -l选项可显示所有任务的PID,jobs的状态可以是running, stopped, Terminated。但是如果任务被终止了(kill),shell 从当前的shell环境已知的列表中删除任务的进程标识。 4、fg 将后台中的命令调至前台继续运行。如果后台中有多个命令,可以用fg %jobnumber(是命令编号,不是进程号)将选中的命令调出。 5、bg 将一个在后台暂停的命令,变成在后台继续执行。如果后台中有多个命令,可以用bg %jobnumber将选中的命令调出。 6、kill 法子1:通过jobs命令查看job号(假设为num),然后执行kill %num 法子2:通过ps命令查看job的进程号(PID,假设为pid),然后执行kill pid 前台进程的终止:Ctrl+c 7、nohup 如果让程序始终在后台执行,即使关闭当前的终端也执行(之前的&做不到,我们用ssh连接的服务器,如果我们断开连接,当前终端上运行的后台任务也就随之关闭,因为我们在当前终端上所运行的所有命令,其父进程都是当前的终端,而一旦父进程退出,则会发送hangup信号给所有子进程,子进程收到hangup以后也会退出。如果我们要在退出shell的时候继续运行进程,则需要使用nohup忽略hangup信号,或者setsid将将父进程设为init进程(进程号为1)),这时候需要nohup,使用nohup之后,如果我们退出终端,我们运行的程序的父进程就会变成1。该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。关闭中断后,在另一个终端jobs已经无法看到后台跑的程序了,因为jobs只能看到当前与当前终端进程有关的程序,此时利用ps可以查看到指定的进程。
但是需要注意的是nohup会关闭我们运行的程序的标准输入流,也就是我们不能只用标准输入流了。默认会重置输出流到nohup.out文件,当然也可以自定义输出文件。
9.9 shell执行程序
我们检查一下shell是如何运行的,以及这于进程组、进程终端和会话概念的关系,再次执行ps命令。
ps -o pid,ppid,pgid,sid,common
zhanglei@zhanglei-virtual-machine:~$ ps -o pid,ppid,pgid,sid,comm
PID PPID PGID SID COMMAND
4463 4453 4463 4463 bash
4504 4463 4504 4463 ps
ps的 父进程是shell,这正是我们期望看到的。不支持作业控制的进程组为前台进程组。
管道中最后一个进程是shell的子进程,该管道中的第一个进程则是最后一个进程的子进程。从中可以看出 shell fork了自身的副本,然后再为副本中每一条命令fork一个进程。
如果在后台执行这个管道
当有作业控制时候,后台作业被放到后台进程组,如果后台作业试图读取控制终端,则会产生SIGTTIN.在没有作业控制时候,其处理方法是:如果这个进程没有重定向输入输出,则会将标准输入、输出重定向到/dev/null。读/dev/null则产生一个文件结束。这就意味着后台进程cat读取到末尾立即正常结束。
9.10孤儿进程组
孤儿进程一般由init收养,一般在父进程已经挂掉了的时候由init收养
父进程沉睡5秒
子进程为挂断信号建立信号处理器(SIGHUP),这样就能观察到SIGHUP是否已经发送给子进程
子进程用kill,给自己发送SIGTSTP,让自身停止
父进程终止以后子进程变为了孤儿进程,父进程变为了init进程。
现在子进程变为了孤儿进程组的成员。将孤儿进程组定义为:该组中每个成员的父进程要么是改组的一个成员,要么不是该组所属会话的成员。对孤儿进程的另一个描述是
父进程停止后进程组包含一个停止的进程,进程组成为孤儿进程组,posix要求向孤儿进程发送sighup信号,接着发送sigcont信号