进程组:
每一个进程除了有一个进程ID外,还属于一个进程组。进程组是一个或是多个进程的集合。每一个进程组有一个唯一的组ID。
每一个进程组都有一个组长进程。组长进程的标识是:其进程组ID就等于其进程ID。组长进程可以创建一个一个进程组,创建该组之间的进程,然后终止。只要某个进程组中用进程存在,那么进程组就存在,与组长进程是否终止无关。
[root@localhost linux]# sleep 100|sleep 200|sleep 300 &
[1] 6293
[root@localhost linux]# ps axj |head -n1
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
[root@localhost linux]# ps axj |grep sleep| grep -v grep
3554 6291 6291 3554 pts/1 6335 S 0 0:00 sleep 100
3554 6292 6291 3554 pts/1 6335 S 0 0:00 sleep 200
3554 6293 6291 3554 pts/1 6335 S 0 0:00 sleep 300
[root@localhost linux]# kill -9 6291
[root@localhost linux]# ps axj |grep sleep| grep -v grep
3554 6292 6291 3554 pts/1 6338 S 0 0:00 sleep 200
3554 6293 6291 3554 pts/1 6338 S 0 0:00 sleep 300
此时可以看到在后台由3 个进程,其中6291是组长进程。 当kill -9 命令杀死组长,发现进程组还在。
注:‘&’:将进程组放在后台执行。
ps:
a:不仅列出当前用户的进程,也列出其他用户的进程。
x:不仅列出有控制端的进程,也列出无控制端的进程。
j:表示列出与作业控制相关的信息。
2.作业
shell分前后台来控制的是不是进程而是作业或者进程组。一个前台作业可以由多个进程组成,后台也可以。shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业,属于进程组。
一旦作业运行结束,shell就会把自己提到前台。如果原来的前台进程还存在,它自动变为后台进程组。
#include<stdio.h>
#include<unistd.h>
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("parents(%d)# i am going to dead...\n",getpid(),i--);
sleep(1);
}
}
return 0;
}
执行程序后会发现,5s后父进程退出了,但子进程依然在输出,即使使用CTRL+c也没有用,换句话说,我们刚起的作业退出了,但是子进程还在,就自动被提到了后台,同时进程组还在。
这时,我们可以重新打开一个会话,查看进程号,杀掉即可,也可以在当前会话杀掉。
3.会话
会话是一个或者多个进程组的集合。一个会话可以有一个控制终端,就类似于开启了一个xshell会话窗口一样。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组以及一个或者多个后台进程组。故:一个会话中应该包括一个控制进程,一个前台进程组和任意后台进程组。
注:注销其实就是指删除会话的过程。
4.作业控制
Shell分前后台来控制的不是进程,而是作业或者是进程组。Shell可以同时运行一个前台作业和任意多个后台作业。
注:jobs 查看当前作业
fg +作业号 可以将作业提到前台(若进程处于停止状态,则进程组给每个信号发送SIGCONT信号让它继续运行)
bg+作业号 将作业放到后台运行
ctrl+z 暂停作业并放在后台 向所有前台进程发送SIGTSTP信号,默认动作是使进程停止
ctrl+c 杀掉作业
5.守护进程
守护进程是运行在后台的一种特殊的进程,它独立于终端且周期的执行某种任务或者等待处理某些发生的事件。Linux中大部分服务器就是用守护进程实现的。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互,且系统服务进程不会受用户登陆注销的影响,它们一直运行着。这样的进程就称为守护进程。
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 2 0 0 ? -1 S 0 0:00 [kthreadd]
2 3 0 0 ? -1 S 0 0:00 [ksoftirqd/0]
2 5 0 0 ? -1 S< 0 0:00 [kworker/0:0H]
2 7 0 0 ? -1 S 0 0:00 [migration/0]
2 8 0 0 ? -1 S 0 0:00 [rcu_bh]
2 9 0 0 ? -1 R 0 0:00 [rcu_sched]
其中TPGID写着-1的都代表守护进程,也就是没有控制终端的进程。大部分守护进程都是孤儿进程,它们自成进程组、作业、会话。
#include <unistd.h>
pid_t setsid(void);
成功返回会话id,失败返回-1.
注意:调用函数之前,必须先确保当前进程不是该进程组的组长,否则返回-1.要不满足其实可以先fork,然后在调用setsid就行。之前说过,如果作业中某个进程创建了子进程,则子进程不属于作业,属于进程组。
#include <unistd.h>
int daemon(int nochdir, int noclose);
#include <unistd.h>
int chdir(const char *path);
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
void mydaemon(void)
{
int i;
int fd0;
pid_t pid;
struct sigaction sa;
umask(0);
//1.将文件模式创建屏蔽字设置为0.
//2.调用fork,父进程退出(exit)
//如果守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令以执行完毕
//同时保证子进程不是一个进程组的组长进程。
if((pid = fork()) < 0)
{
perror("fork");
}
else if(pid > 0)
{
exit(0);//父进程终止
}
setsid();//3.创建一个会话。
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
if(sigaction(SIGCHLD,&sa,NULL) < 0)
{
return;
}
//注意:再次fork,保持子进程不是控制终端,并且保证后续不会同其他终端关联(可选择)
if((pid = fork()) < 0)
{
perror("fork");
return;
}
else if(pid != 0)
{
exit(0);
}
if(chdir("/") < 0)//更改当前目录为根目录
{
perror("child chdir");
}
close(0);
fd0 = open("/dev/null",O_RDWR);
dup2(fd0,1);
dup2(fd0,2);
}
int main()
{
mydaemon();
while(1)
{
sleep(1);
}
}