进程组:
进程组是一个或多个进程的集合。通常与一个作业相关联,可以接收来自同一终端的各种信号。我们知道,每个进程都有一个进程ID存放在task_struct中,进程组也有进程组ID,是唯一的。一个线程组拥有主线程,主线程ID就是线程组ID,类似地,进程组也有一个组长进程,组长进程的ID就是进程组ID。组长进程可以创建一个进程组,创建组中的进程,然后终止。只要某一个进程存在,进程组就存在,不会随着组长进程的退出而消失,只有组中所有进程都退出时,进程组才会退出。
我们可以看到,目前有三个进程,&的作用的将进程组放到后台运行。进程ID分别是 2826 2827 2828组长ID PGID=2826。可以自己测试一下,使用kill命令杀掉组长进程之后,进程组还在。
ps 命令: -a 列出所有用户的进程。 -x 不仅列出有终端控制的进程,也列出无终端控制的进程。 -j 表示列出与作业相关的信息。
作业:
一个作业可以包含一个或多个进程,尤其是使用了管道和重定向之后,列如上面的列子,sleep 100|sleep 200|sleep 300 这条命令就同时启动了三个进程,属于一个作业。
shell分前后台运行的不是进程,而是作业和进程组。作业和进程组的区别:作业中某个进程创建的子进程不属于作业。
shell可以运行一个前台作业和任意个后台作业。当我们创建一个作业时,shell会到后台,无法运行shell(不能执行新输入的命令)。作业运行结束后,shell将提到前台,此时可以输入命令运行shell。我们来看一段代码。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{//child
while(1)
{
printf("child(%d)# I am running!\n", getpid());
sleep(1);
}
}
else
{ //parent
int i = 5;
while(i)
{
printf("parent(%d)# I am going to dead ... %d\n", getpid(), i--);
sleep(1);
}
}
return 0;
}
我们可以发现程序运行之后,我们无法通过shell运行其他命令。fork 创建了一个子进程,父进程5s后会退出,但是子进程还存在。因为子进程不属于这个作业,所以父进程退出后,作业也就退出了。这个时候,shell已经提到前台,可以执行新的命令。但是子进程还存在,进程组也存在(只是组长进程,即父进程退出了),此时这个子进程被放到后台。
会话:Session
会话是一个或多个进程组的集合。一个控制终端下可以有一个会话。建立控制终端与会话连接的进程叫做会话首进程(控制进程),一个会话可以有一个前台进程组和多个后台进程组。
当我们新打开一个终端时:
我们可以看到有一个 SID 这个就是会话ID 可以看到这个ID是2811,那么这个2811是谁?
![](https://i-blog.csdnimg.cn/blog_migrate/185fa2463be204a5493c133c170fda58.png)
我们可以看到,这就是我们的bash,就是会话首进程,也就是这三个进程的父进程。
作业控制:
第一步:我们首先创建两个后台作业1和2,并用jobs命令查看作业。
第二步:fg 命令可以将某个后台作业提到前台运行,如果该作业正在后台运行,则直接提到前台运行,如果改作业在后态是停止状态,则向该作业发送信号SIGCONT使它继续运行。参数 1 表示操作的作业是第一个。
第三步:Ctrl + Z是向所有前台进程发送信号SIGTSTP,使进程停止。然后再用jobs命令查看作业。
第四步:bg的作用是将某个停止的作业在后台继续运行,会给所有进程发送信号SIGCONT。然后jobs查看作业。
第五步:将作业1提到前台运行。
第六步:Ctrl + C 终止整个作业。 而不是单个进程。然后用jobs查看只剩下一个后台作业了。
守护进程:
守护进程是运行在后台的一种特殊进程。它独⽴于控制终端并且周期性地执⾏某种任务或等待处理某些发⽣的事件。守护进程是⼀种很有⽤的进程。Linux的⼤多数服务器就是⽤守护进程实现的。⽐如,ftp服务器,ssh服务器,Web服务器httpd等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和⽤户交互。其它进程都是在⽤户登录或运⾏程序时创建,在运⾏结束或⽤户注销时终⽌,但系统服务进程(守护进程)不受⽤户登录注销的影响,它们⼀直在运⾏着。这种进程有⼀个名称叫守护进程(Daemon)。
我们可以通过命令 ps -axj查看。
我们可以看到,TPGID为-1的都是守护进程。
在COMMAND⼀列⽤[]括起来的名字表⽰内核线程,这些线程在内核⾥创建,没有⽤户空间代码,因此没有程序⽂件名和命令⾏, 通常采⽤以k开头的名字,表⽰Kernel。
创建守护进程:
创建守护进程需要用setsid来创建一个新的会话。该进程成为会话首进程。
#include <unistd.h>
pid_t setsid(void);
该函数调⽤成功时返回新创建的会话的id(其实也就是当前进程的id),出错返回-1。
注意,调⽤这个函数之前,当前进程不允许是进程组的组长进程,否则该函数返回-1。要保证当前进程不是进程组的组长进程也很容易,只要先fork再调⽤setsid就⾏了。fork创建的⼦进程和⽗进程在同⼀个进程组中,进程组的组长进程必然是该组的第⼀个进程,所以⼦进程不可能是该组的第⼀个进程,在⼦进程中调⽤setsid就不会有问题了。
调用该函数成功后:
创建⼀个新的会话,当前进程成为会话首进程,当前进程的id就是Session的id。
创建⼀个新的进程组,当前进程成为进程组的组长进程,当前进程的id就是进程组的id。
如果当前进程原本有⼀个控制终端,则它失去这个控制终端,成为⼀个没有控制终端的进程。