1.作业和进程组
进程组:每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。
性质:
(1)每个进程组都有一个唯一的进程组ID,存放在pid_t数据类型中,函数getpgrp返回调用进程的进程组ID。
(2)每个进程组都有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。
(3)进程组是否存在与其组长进程是否终止无关。进程组的生命周期从创建开始到其最后一个进程离开为止的时间区称为进程组的生命周期。某个进程的最后一个进程既可以终止,又可以参加另一个进程组。
作业:Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。 前台作业和后台作业可以由多个进程组成,Shell可以运一个前台 作业和任意多个后台作业,这称为作业控制。
进程组和作业的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。
注:Ctrl + C等组合键只能对前台进程产生作用,不能对后台进程产生作用
fg + 作业号 表示把作业提到前台
Ctrl + Z前台作业暂停,bg + 作业号 将后台作业提到前台
bash本身自成一个进程组
jobs 查看所有的后台进程
测试:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <error.h>
int main()
{
pid_t id=fork();
if(id==0)
{
while(1)
{
printf("child:mypid->%d\n",getpid());
sleep(3);
}
}
else if(id>0)
{
int i=0;
while(i<5)
{
i++;
printf("father:mypid->%d\n",getpid());
sleep(2);
}
}
else
{
perror("fork error\n");
}
return 0;
}
结果分析:
2.会话
会话是一个或多个进程组的集合。SID为会话ID。
注:会话特性:
(1)一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或 伪终端设备(在网络登陆情况下)。
(2)建立与控制终端连接的会话首进程被称为控制进程。
(3)一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。
(4)无论何时键入中断键(通常是Ctrl+C等组合键)或退出键,就会造成将中断信号或退出信号送至前台进程组的所有进程。
3.线路规程
线路规程是一个过滤器,是内核处理终端设备的模块。
4.守护进程
守护进程也称精灵进程(Daemon),是运
行在后台的一种特殊进程。它独立于控制终端并 且周期性地执行某种任务或等待处理某些发生的事件。提供7×24小时服务,不受用户登录和注销的影响。
创建守护进程最关键的一步是调用setsid函数创建这
个新的Session,并成为Session Leader。
Session函数成功调用的结果是
(1)调用进程成为新会话的首进程。
(2)调用进程成为一个进程组的组长进程 。
(3)调用进程没有控制终端(与当前终端取消关联)。
创建守护进程的过程:
1.调用umask将文件模式创建屏蔽字设置为0.
2.调用fork,父进程退出(exit)。
原因:
a:如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止,
使得shell认为该命令已经执行完毕。
b:保证子进程不是一个进程组的组长进程。
3. 调用setsid创建一个新会话。
4. 将当前工作目录更改为根目录。(a:方便用户寻找;b:防止当前目录被删除)
5. 关闭不在需要的文件描述符。
6. 其他:忽略SIGCHLD信号。
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void mydaemoc()
{
umask(0);
//fork子进程
if (fork()>0)
{
exit(0);
}
//新建会话
setsid();
//更改工作目录
chdir("/");
//关闭文件描述符表
close(0);
close(1);
close(2);
//忽略SIGCHLD信号
signal(SIGCHLD, SIG_IGN);
}
int main()
{
mydaemoc();
while (1);
return 0;
}
结果分析:
使
也可以使用daemon函数创建守护进程
#include <unistd.h>
int daemon(int nochdir, int noclose);
默认缺省参数为0,0,
当nochdir为零时代表改变工作目录为根目录。
当 noclose为零时,标准输入、标准输出和错误输出重导向为/dev/null,也就是不输出任何信息,否则照样输出。
返回值:
deamon()调用了fork(),如果fork成功,那么父进程就调用_exit(2)退出,所以看到的错误信息全部是子进程产生的。如果成功函数返回0,否则返回-1并设置errno
deamon()调用了fork(),如果fork成功,那么父进程就调用_exit(2)退出,所以看到的错误信息全部是子进程产生的。如果成功函数返回0,否则返回-1并设置errno
5.创建守护进程fork的作用
第一次fork:这里第一次fork的作用就是让shell认为这条命令已经终止,不用挂在终端输入上;再一个是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长(会报错Operation not permitted),如果不fork子进程,那么此时的父进程是进程组组长,无法调用setsid。所以到这里子进程便成为了一个新会话组的组长。
第二次fork(非必要):第二次fork是为了避免后期进程误操作而再次打开终端。因为打开一个控制终端的前提条件是该进程必须为会话组组长,而我们通过第二次fork,确保了第二次fork出来的子进程不会是会话组组长。