进程间关系
进程组/作业/会话
进程组
- 进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号。
- 每个进程除了有一个进程ID之外,还属于一个进程组。
- 每个进程组都有唯一的进程组ID(整数,也可以存放在pid_t类型中)。
- 进程组,每个进程组有一个领头进程。
- 进程组由进程组ID来唯一标识。
- 每个进程组都有一个组长进程,组长进程的进程号等于进程组ID。组长进程可以创建一个进程组、创建该组中的进程。只要某个进程组中有一个进程存在,则该进程组就存在,与组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间成为进程组的生存期。进程组中最后一个进程可以终止或者转移到另一个进程组中。
例如:
进程:17157 ,17158,17159
进程组长:17157 进程组当中的第一个进程
&:表示将进程组放到后台执行
ps选项:
- a:不仅可以列出当前用户的进程,也可以列出所有其他用户的进程。
- x:表示不仅列有控制终端的进程,也列出所有无控制终端的进程。
- j:表示列出与作业控制相关的信息。
作业
- Shell分前后来控制的不是进程而是作业或者进程组。
- 一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成。
- Shell可以运行一个前台作业和任意多个后台作业。—作业控制
- 在前台新起作业,shell是无法运行,因为他被提到了后台。但是如果前台进程退出,shell就又被提到了前台,所以可以继续接受用户输入。
作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业
例:
int main()
{
pid_t id=fork();
if(id<0)
{
perror("fork");
return -1;
}
else if(id==0)
{
while(1)
{
printf("child (%d) # i am running!\n",getpid());
sleep(1);
}
}
else
{
int i=5;
while(i)
{
printf("parent(%d)# i am going to dead...%d\n",getpid(),i--);
sleep(1);
}
}
return 0;
我们可以看到,程序跑起来后,在前台新起了1个作业,包含父子两个进程。5s之后,shell无法接受任何命令,说明此时的前台作业不是shell。
但当父进程退出后,子进程还在运行,但此时输入的命令,shell可以处理,说明此时shell变成了前台作业。
但子进程所属的进程组还在,组长是父进程(已退出),杀掉即可。
作业控制
进程前后台操作用到以下命令或按键:
Ctrl+C:终止并退出前台命令的执行,回到SHELL
Ctrl+Z:暂停前台命令的执行,将该进程放入后台,回到SHELL
jobs:查看当前在后台执行的命令,可查看命令进程号码
&:运行命令时,在命令末尾加上&可让命令在后台执行
fg N:将命令进程号码为N的命令进程放到前台执行,同%N
bg N:将命令进程号码为N的命令进程放到后台执行
会话
- 会话是一个或多个进程组的集合。
- 一个会话可以有一个控制终端。
- 建立与控制终端连接的会话首进程被称为控制进程。
- 一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。
所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。
SID:会话id
守护进程
守护进程也叫精灵进程,是在后台运行的一种特殊进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件;
用户使守护进程独立于所有终端是因为,在守护进程从一个终端启动的情况下,这同一个终端可能被其他的用户使用。例如,用户从一个终端启动守护进程后退出,然后另外一个人也登录到这个终端。用户不希望后者在使用该终端的过程中,接收到守护进程的任何错误信息。同样,由终端键人的任何信号(例如中断信号)也不应该影响先前在该终端启动的任何守护进程的运行。虽然让服务器后台运行很容易(只要shell命令行以&结尾即可),但用户还应该做些工作,让程序本身能够自动进入后台,且不依赖于任何终端。
守护进程没有控制终端,因此当某些情况发生时,不管是一般的报告性信息,还是需由管理员处理的紧急信息,都需要以某种方式输出。Syslog 函数就是输出这些信息的标准方法,它把信息发送给 syslogd 守护进程。
//用ps axj 查看系统中的进程。
ps axj | more
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:04 /sbin/init
0 2 0 0 ? -1 S 0 0:00 [kthreadd]
2 3 0 0 ? -1 S 0 0:00 [migration/0]
2 4 0 0 ? -1 S 0 0:03 [ksoftirqd/0]
2 5 0 0 ? -1 S 0 0:00 [stopper/0]
2 6 0 0 ? -1 S 0 0:09 [watchdog/0]
2 7 0 0 ? -1 S 0 0:09 [events/0]
2 8 0 0 ? -1 S 0 0:00 [cgroup]
2 9 0 0 ? -1 S 0 0:00 [khelper]
2 10 0 0 ? -1 S 0 0:00 [netns]
2 11 0 0 ? -1 S 0 0:00 [async/mgr]
2 12 0 0 ? -1 S 0 0:00 [pm]
2 13 0 0 ? -1 S 0 0:00 [sync_supers]
2 14 0 0 ? -1 S 0 0:00 [bdi-default]
2 15 0 0 ? -1 S 0 0:00 [kintegrityd/0]
2 16 0 0 ? -1 S 0 0:05 [kblockd/0]
2 17 0 0 ? -1 S 0 0:00 [kacpid]
2 18 0 0 ? -1 S 0 0:00 [kacpi_notify]
2 19 0 0 ? -1 S 0 0:00 [kacpi_hotplug]
2 20 0 0 ? -1 S 0 0:00 [ata_aux]
2 21 0 0 ? -1 S 0 0:33 [ata_sff/0]
2 22 0 0 ? -1 S 0 0:00 [ksuspend_usbd]
2 23 0 0 ? -1 S 0 0:00 [khubd]
2 24 0 0 ? -1 S 0 0:00 [kseriod]
2 25 0 0 ? -1 S 0 0:00 [md/0]
创建守护进程
setsid函数
#include <unistd.h>
id_t setsid(void);
注意:调用该函数之前,当前进程不允许是进程组的组长,否则返回-1。
先fork再调用。fork创建的子进程和父进程在同一个进程组中,进程组的组长必然是该组的第一个进程,故子进程不可能是该组的第一个进程。
成功调用该函数的结果是:
1.当前进程成为会话首进程(控制进程),当前进程的 id 就是会话的id。
2.创建一个新的进程组,当前进程成为进程组的组长,当前进程的 id 就是进程组的 id。
3.如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。
4.所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
代码:
//实现守护进程
//
//
void mydaemon()
{
int pid=-1;
umask(0);
pid=fork();
//让子进程到后台运行
if(pid>0)
{
exit(0);
}
else if(pid<0)
{
perror("fork");
}
//为子进程创建会话
if(setsid()<0)
{
exit(-1);
}
chdir("/");//改变当前工作目录
//防止子进程与其他进程关系起来
if(pid>0)
{
exit(0);
}
else if(pid<0)
{
perror("fork");
}
close(0);
close(1);
close(2);
}
int main()
{
mydaemon();
while(1)
{
sleep(1);
}
return 0;
}
系统提供的daemon函数:
#include <unistd.h>
int daemon(int nochdir, int noclose);
// 参数
// 当 nochdir为 0 时,当前目录变为根目录,否则不变;
// 当 noclose为 0 时,标准输入、标准输出和错误输出重定向为/dev/null,也就是不输出任何信息,否则照样输出。
// 返回值:
// deamon()调用了fork()
// 如果fork成功,那么父进程就调用_exit(2)退出,
// 所以看到的错误信息 全部是子进程产生的。
// 如果成功函数返回0,否则返回-1并设置errno。
#include <stdio.h>
#include <unistd.h>
int main()
{
daemon(0, 0);
while(1);
return 0;
}