物联网学习进程——Linux进程间关系和守护进程
一、实验说明
1.回顾
上一节课我们已经学习了Linux系统编程之进程控制。今天让我们来学习Linux进程间关系和守护进程。
2. 环境介绍
本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌面上的程序:
LX终端(LXTerminal): Linux命令行终端,打开后会进入Bash环境,可以使用Linux命令。常用Linux命令大全
gedit:非常好用的编辑器.gedit使用方法
GVim:非常好用的编辑器,最简单的用法可以参考课程Vim使用方法
3. 环境使用
#首先点击“开始试验”
开始实验后,按照实验步骤在环境中进行操作,每完成一步点击底部的 下一步;学习过程中可以将心得收获记录在页面上方的 实验报告,遇到问题可以直接点击页面上方 我要提问 进行提问。实验操作界面包含两栏,左边栏为实验步骤、实验报告,右边栏为虚拟机环境(部分课程是 Web IDE 或 Jupyter Notebook)和工具栏。
右边栏工具栏中有很多实验中可能用到的功能,可以点击一一尝试:
使用GVim编辑器输入实验所需的代码及文件,使用LX终端(LXTerminal)运行所需命令进行操作。
此处输入链接的描述
实验报告页面可以在“我的主页”中查看,其中含有每次实验的截图及笔记,以及实验的有效学习时间(指的是在实验桌面内操作的时间,如果没有操作,系统会记录为发呆时间),这些都是您学习的真实性证明。
二、实验过程
2.1进程组
实验原理
每个进程除了有一个进程ID外,还属于一个进程组。进程组是一个或多个进程的集合。通常,他们与同一个作业相关联,他们可以接受来自于同一个终端的信号。每个进程组有一个唯一的组ID,每个进程组都可以有一个组长进程,进程组ID就是组长的ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中一个进程存在,该进程组就存在,这与组长进程是否终止无关。
将组长进程杀死后查看,发现进程组并没有被影响到,这也印证了我们上面的说法,组长进程存在与否不影响进程组。
作业
Shell分前后台来控制的不是进程而是zuoye(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,Shell可以运行一个前台作业和多个后台作业,这称为作业控制。
作业与进程组的区别:如果作业中的某个进程创建出了一个子进程,则子进程不属于作业。
一旦作业运行结束,Shell就把自己提到前台(子进程还在,但它不属于作业),如果原来的前台进程还存在(如果这个子进程还没终止),它自己变为后台进程组。
接下来我们编写源代码说明父进程退出后,子进程还在运行。
创建代码文件
#include
#include
int main()
{
pid_t id = fork();
if(id<0)
perror("fork\n");
else if(id==0)
{ while(1){
printf("I am child(%d),i am running...\n",getpid());
sleep(4);}}
else
{
int i = 5;
printf("I am father(%d),i am going to dead...%d\n",getpid(),i--);
sleep(4);
}
return 0;
}
保存为2.cpp ,在终端输入 g++ 2.cpp -o 2.out
然后我们在 Xfce 终端输入 ./2.out
思考
当这段代码运行起来后,为什么我们可以发现5s之内shell无法接受任何指令?
2.2守护进程实验原理
实验原理
守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件,守护进程是一种很有用的进程。Linux大多数服务器就是用守护进程实现的。比如:ftp服务器,ssh服务器,Web服务器httpd等。同时,守护进程完成很多系统任务,比如,作业规划进程crond等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行时创建的,在用户结束或注销时终止,但系统服务进程不受用户登录的影响,它一直运行着,这种进程被称为守护进程。
创建守护进程
创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
pid_t setsid(void);
该函数调用成功时返回新创建的Session的ID,出错返回-1,调用此函数之前,当前进程不允许是进程组的Leader。为了保证函数不出错,我们利用fork函数创建一个子进程,在子进程中调用它就可以了。
成功调用该函数后:
创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。
编写源代码
#include
#include
#include
#include
#include
#include
#include
void mydaemon()
{
umask(0);
pid_t n = fork();
if(n<0)
perror("fork\n");
else if(n>0)
exit(1);
pid_t m = setsid();
printf("sid = %d\n",m); //打印出守护进程的ID
chdir("/"); //重定向到根目录
close(0); //关闭不需要的文件描述符
close(1);
close(2);
signal(SIGCHLD,SIG_IGN);
}
int main()
{
mydaemon();
while(1){}
return 0;
}
思考:
保存为3.cpp ,在终端输入 g++ 3.cpp -o 3.out 后
多次运行./3.out结果如何?如何直接调用daemon函数来创建一个守护进程?
三、实验结果(每个例子都要有源代码和效果图,有调试图)
2.1 原代码
#include
#include
int main()
{
pid_t id = fork();
if(id<0)
perror("fork\n");
else if(id==0)
{ while(1){
printf("I am child(%d),i am running...\n",getpid());
sleep(4);}}
else
{
int i = 5;
printf("I am father(%d),i am going to dead...%d\n",getpid(),i--);
sleep(4);
}
return 0;
}
2.1 截图
2.1 结论分析
通过实验结果截图得知,线程组创建之后5s内无法操作shell,当进程组长消亡后,组员(子线程)会一直运行在系统后台,如果没有相关操作,会一直运行下去,打印 i am child(281),i am running...的语句,直达内存完全占有用,停止打印。可见组长的生命周期不会影响到组员的生命周期。
2.2 原代码
#include
#include
#include
#include
#include
#include
#include
void mydaemon()
{
umask(0);
pid_t n = fork();
if(n<0)
perror("fork\n");
else if(n>0)
exit(1);
pid_t m = setsid();
printf("sid = %d\n",m); //打印出守护进程的ID
chdir("/"); //重定向到根目录
close(0); //关闭不需要的文件描述符
close(1);
close(2);
signal(SIGCHLD,SIG_IGN);
}
int main()
{
mydaemon();
while(1){}
return 0;
}
2.2 截图
2.2 结论分析
根据实验结果截图分析,第一次运行打印sid为351,多次运行后,每次打印的sid否都有不同,可见pid_t setsid(void)成功调用后,创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。