在操作系统中,进程具有独立性!但除了进程,操作系统还通过别的方式进行管理。
前面提到了一个很重要的思想,管理。一些系统中需要进行管理的进程,文件,目录等等,都可以抽象为,先描述,再组织。通过这样的方式来进行管理!
就像为了提高并发性,引入了线程的概念。
为了更好的管理进程,Linux下还引入了一个概念,叫做进程组。
每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常,它们与同一作业相关联可以接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。每个进程组都可以有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只有在某个进程组中一个进程存在,则该进程组就存在,这与组长进程是否终止无关。
12863,12864,12865就是开启的进程,组长是12863,是进程组中的第一个进程。用kill -9杀掉组长,进程组还在。
除了进程组的概念,还有一个作业的概念。
在绝大多数情况下,作业和进程组是等价的。Shell分前后台来控制的不是进程而是作业或者进程组。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成。Shell可以运行一个前台作业和任意多个后台作业,这称之为作业控制。
作业和进程组只有一点微小的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业。
一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还在,它自动变为后台进程组。
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
while(1)
{
printf("child(%d):I am a running process!!!\n",getpid());
sleep(1);
}
}else{
int i = 5;
while(i--)
{
printf("parent(%d)# I am father!!!\n",getpid());
sleep(1);
}
}
return 0;
}
运行这段代码之后,你会发现,前台起了一个作业,包含父子两个进程。5s之内,Shell无法接受任何命令!说明此时的前台作业不是shell。
但父进程退出后,子进程还在运行,而shell已经可以接受命令了!
也就是说:刚才新启的作业退出了,但子进程还在,被自动提到了后台。
这样我们就大致明白了,操作系统在内核中,如何进行管理。
线程是最小的调度单位,进程是最小分配资源的单位,一个前台运行的并非一个进程,而是作业或进程组。
也就是说,线程组成进程,进程组成作业。那么有没有比作业更大的概念呢?事实上,是有的。这就是会话。
会话是一个或多个进程组的集合。一个会话可以有一个控制终端。这通常是登录到其上的终端设备或伪终端设备。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程可被分为一个前台进程以及多个后台进程组。所以一个会话中,应该包括控制进程,一个前台进程组和任意多个后台进程组。
SID表示的就是会话ID,在上图中12773,三个进程都属于同一个进程组,同一个会话。
多打开几个终端,你就会发现,每打开一个终端,就新建了一个会话。
守护进程
守护进程也被称之为精灵进程,是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程来实现的。如ftp服务器,ssh服务器,Web服务器。同时,守护进程完成许多系统任务。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或者运行程序时创建,在运行结束或用户注销时终止,但服务进程(守护进程)不受用户登录注销的影响,它们一直在运行着。这种进程就是守护进程。
那么如何来查看守护进程呢?
用ps axj | more
就可以看到。参数a表示不仅当前列用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
那么如何来创建守护进程呢?
创建守护进程最关键的一步就是调用setsid函数创建一个新的会话,并成为会话的会话首进程。
#include<unistd.h>
pid_t setsid(void);
值得注意的是,调用这个函数之前,当前进程不允许是进程组的Leader,否则函数返回-1。要保证当前进程不是进程组的Leader也很容易,只需fork在调setsid即可。
成功调用该函数的结果是:
- 创建一个新的会话,成为会话首进程,当前进程id就是会话的id。
- 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
- 如果当前进程原本有一个终端,则它失去这个控制终端,成为一个没有控制终端的进程。
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
void mydaemon()
{
int i;
int fd0;
pid_t pid;
struct sigaction sa;
umask(0);
if((pid = fork()) < 0)
{
perror("fork");
}else if(pid > 0){
exit(0);
}
setsid();
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGCHLD, &sa, NULL) < 0){
return;
}
if((pid = fork()) < 0){
perror("fork");
return;
}else if(pid != 0){
exit(0);
}
if(chdir("/") < 0){
perror("chdir");
return;
}
close(0);
fd0 = open("/dev/null",O_RDWR);
dup2(fd0, 1);
dup2(fd0, 2);
}
int main()
{
mydaemon();
while(1){
sleep(1);
}
return 0;
}
我们可以进行总结,创建一个守护进程只需要以下几步:
- 进行一次fork
- exit退出父进程
- 子进程进行setsid
- 关闭文件描述符
- 将目录切换至根目录,然后循环执行