【Linux】进程exec函数族以及守护进程

一.exec函数族

1.exec函数族的应用

        在shell下敲shell的命令都是在创建shell的子进程。而我们之前学的创建父进程和子进程代码内容以及通过pid与0的关系来让父子进程执行不同的代码内容都是在一个代码文件里面,而shell是如何做到不在一个文件里面写代码使之成为子进程的呢?

        答案是使用了exec函数族

假如现在有段在shell下敲的名为test的代码,执行过程为:父进程(shell)fork()出一个子进程后,子进程调用exec函数族执行test,此时原进程的内容就会被覆盖,执行test的内容,就实现了创建新的进程并在该进程中执行不同的程序;

这些函数允许将当前进程内容替换为新的可执行文件,从而实现进程的代码和数据的切换。

进程调用exec函数族执行某个程序

进程当前内容被指定的程序替换

实现让父子进程执行不同的程序 :

●父进程创建子进程  

●子进程调用exec函数族  

●父进程不受影响

2.exec函数族的一些常见成员

(1)excel / excelp(熟练)

#include  <unistd.h>
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);

 成功时执行指定的程序;失败时返回EOF

 path   执行的程序名称,包含路径

 arg…  传递给执行的程序的参数列表

 file   执行的程序的名称,在PATH中查找(PATH为linux的环境变量,添加到系统的环境变量后,在系统的任意路径都能直接输入程序名称执行,不用输入路径)

execl : 以参数列表的方式执行新程序。需要指定可执行文件的路径,以及传递给新程序的参数列表。

进程创建 – execl(p) – 示例:

 ⭕ 执行ls命令,显示/etc目录下所有文件的详细信息

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	if(execl("/bin/ls","ls","-a","-l","/etc",NULL)<0)
	{
	 	perror("execl");
	}

	return 0;
}
#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	if(execlp("ls","ls","-a","-l","/etc",NULL)<0)
	{
	 	perror("execl");
	}

	return 0;
}

分析:

execl中:

"/bin/ls"为执行的程序名称,在/bin路径下有个叫ls的程序;

"ls"是第0个参数,第0个参数通常是程序的名称,第0个参数必须要写,虽然它没有使用;

"-a","-l","/etc"都是传递给程序的参数;

execlp中:除了直接传入程序名称在系统路径中查找以外,其余和execl相同;

注意:两个函数区别在于execlp不需要写文件名全路径,在系统路径PATH查找

            最后一个参数都必须用空指针(NULL)作结束

            进程当前内容被指定的程序替换,但进程号不变

            第0个参数必须要写,虽然它没有使用

(2)execv / execvp (熟练)

  #include  <unistd.h>
  int execv(const char *path, char *const argv[]);
  int execvp(const char *file, char *const argv[]);

成功时执行指定的程序;失败时返回EOF  ;

arg… 封装成指针数组的形式;

这两个就是把参数列表封装成了一个数组;

进程创建 – execv(p) – 示例

⭕执行ls命令,显示/etc目录下所有文件的详细信息

伪代码如下:

  char  *arg[] = {“ls”, “-a”, “-l”, “/etc”, NULL};
  
  if  (execv(“/bin/ls”, arg) < 0)
 {
     perror(“execv”);
  }  
  
  if  (execvp(“ls”, arg) < 0)
 {
     perror(“execvp”);
  }  

注意:末尾的NULL照样不能省;

3.system(exec的简略版本) (熟练)

内部使用了 exec 函数族的一种方式来执行 shell 命令

#include  <stdlib.h>
int system(const char *command);
 

成功时返回命令command的返回值;失败时返回EOF

当前进程system 函数会创建一个子进程来执行 shell 命令,等待执行结束后才继续执行

system 函数一般用来执行shell命令,并不适用于直接执行可执行程序,一般使用exec函数族来执行可执行程序。

#include <stdlib.h>

int main(int argc, const char *argv[])
{
	system("/bin/ls -a -l /etc");

	return 0;
}

二.守护进程

1.守护进程的特点(了解)

(1)守护进程

守护进程(Daemon Process)是Linux三种进程类型之一;

是 Linux 中的后台服务进程;

是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件;

(2)守护进程特点

始终在后台运行

独立于任何终端

周期性的执行某种任务或等待处理特定事件

它是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭

举例:

http 服务的守护进程叫 httpd,mysql 服务的守护进程叫 mysqld。

2.会话、控制终端(了解)

进程组(Process Group): 进程集合,每个进程组有一个组长(Leader),其进程 ID 就是该进程组 ID。

会话(Session): 进程组集合,每个会话有一个组长,其进程 ID 就是该会话组 ID。

控制终端(Controlling Terminal):每个会话可以有一个单独的控制终端,与控制终端连接的 Leader 就是控制进程(Controlling Process)。

即进程的集合为进程组,进程组集合为会话组 ,会话组的控制终端为控制进程;

3.创建守护进程流程(熟练)

简便地创建守护进程: nohup 命令

nohup  xxxx  &

nohup在某些情况下是方便的,但是需要创建更稳定和更高级需求的守护进程时并不推荐nohup;

接下来我们介绍学习另外的方法:

(1)创建子进程,父进程退出

 if (fork() > 0)  
{
    exit(0);
}

子进程变成孤儿进程,被init进程收养  

子进程在后台运行

(2)子进程创建新会话

setsid函数:
 

#include <unistd.h>

pid_t setsid(void);

成功:返回调用进程的会话ID;失败:-1,设置errno。

作用:用于创建一个新的会话,并将调用进程设置为这个新会话的领导进程(session leader),同时也是一个新进程组的组长。这使得进程完全脱离原始的控制终端,而且它的子进程将成为这个新会话和进程组的成员
调用了setsid函数的进程,既是新的会长,也是新的组长;

if(setsid()<0)
{
    perror("setsid:");
    exit(-1);
}

此时

子进程成为新的会话组长  

子进程脱离原先的终端

(3)更改当前工作目录

守护进程一直在后台运行,其工作目录不能被卸载要具有稳定性,因此最好重新设定一个稳定的工作目录;

chdir("/");
chdir("/tmp");

(4)重设文件权限掩码

umask() 是一个用于设置文件创建权限掩码的系统调用函数。当进程创建新文件或目录时,会根据当前的文件创建权限掩码来确定新文件的权限;

if(umask(0)<0)
{
    exit(-1);
}

 文件权限掩码设置为0;

只影响当前进程创建的新文件的权限;

(5)关闭打开的文件描述符

在脱离终端控制后,stdin / stdout / stderr无法再使用,所以需要把这三个的文件描述符关闭,标准输入(0)、标准输出(1)、和标准错误(2)

int  i;
for(i=0; i<3; i++) 
{
    close(i); 
}

 关闭所有从父进程继承的打开文件  

已脱离终端,stdin / stdout / stderr无法再使用

(6)步骤总结

第一步:用exit()将父进程退出,使子进程成为孤儿进程被init进程收养;
第二步:子进程利用setsid()函数创建新的会话,脱离原来的终端,成为会话组长;
第三步:使用chdir()更改工作的目录;
第四步:使用umask()重设文件权限掩码;
第五步:关闭文件描述符。
第一步可用nohup xxx  & 命令进行,但是不推荐;
第一二步是必须完成的,后面是根据自己的实际情况进行的。

4.守护进程—示例

 创建守护进程,每隔1秒将系统时间写入文件time.log

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
	
	pid_t pid;
	FILE *fp;
	time_t t;
	int i;
	 
	if((pid = fork())<0)
	{
	   perror("fork");
	   exit(-1);
	}
	else if(pid>0)
	{
	  exit(0);
	}

	setsid();
	umask(0);
	chdir("/tmp");
	close(0);
	close(1);
	close(2);

	if((fp = fopen("time.log","a")) == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	while(1)
	{
		time(&t);
		fprintf(fp,"%s\n",ctime(&t));
		fflush(fp);
		sleep(1);
	}
	return 0;
}
上一篇:风电厂数字孪生3D数据可视化交互展示构筑智慧化电厂管理体系