【linux系统编程学习笔记】第二节:进程的应用场景分析

我的梦境 提交于 2020-08-13 01:19:15

认真和耐心总会帮你解决大部分难题。

进程的应用场景

  • 调用第三方程序
  • 守护进程/精灵进程(服务)

调用第三方程序相关API

exec系列函数

#include <unistd.h>

int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

相关概念:

  • 给本进程加载指定的程序,如果成功,本进程的整个内存空间都被覆盖

  • 执行指定程序之后,会自动获取原来的进程的环境变量。

  • 各个后缀字母的含义:
    – l : list 以列表的方式来组织指定程序的参数
    – v: vector 矢量、数组,以数组的方式来组织指定程序的参数
    – e: environment 环境变量,执行指定程序前顺便设置环境变量
    – p: 专指PATH环境变量,这意味着执行程序时不需要全路径



  • 这组函数只是改变了进程的内存空间里面的代码和数据,但并未改变本进程的其他属性,比如PID,比如跟其他进程的父子关系。

函数功能:

  • 加载ELF文件或者脚本,即调用第三方程序
  • execl:调用一个指定路径下的应用程序,传输给指定应用程序的参数通过后面的一个一个字符串传输
  • execlp:调用一个命令,传输给命令的参数通过后面的一个一个字符串传输
  • execv:调用一个指定路径下的应用程序,传输给指定应用程序的参数通过字符串数组传输
  • execvp:调用一个命令,传输给命令的参数通过字符串数组传输

函数参数:

  • path:参数表示你要启动程序的名称包括路径名
  • file:指向可执行文件名称,因为execlp()函数会从系统PATH路径中寻找相应命令,所以不需要带完整路径
  • arg:参数表示启动程序所带的参数,一般第一个参数为要执行命令名,不是带路径且arg必须以NULL结束
  • argv:多个传入参数所形成的一个字符串数组,而且最后一个元素必须以NULL指针作为结尾

返回值:

  • 成功则没有返回值,因为调用这个函数的原本的程序会被覆盖掉,失败则返回-1,errno会被设置

例程
exec系列函数放在一个历程里,看起来或许有些繁琐。

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

int main(void)
{
	pid_t cpid;
	printf("exec start\n");		
	cpid = vfork();//创建出来的子进程是不会复制父进程的资源到子进程中,从而配合exec系列去调用别人家的的程序的时候提高了调用的效率
	if(cpid == 0)
	{
/*
1、函数原型
	int execl(const char *path, const char *arg, ...);
	
	execl(f1,f2,f3,f4,...)
	f1:需要跳转执行的文件
	f2:文件执行的名字或者别名(执行过程可以用另外一个终端执行ps -aux查看)
	f3:传入的参数(看自己main函数是否有开参数传入的接口)
	f4:可以传入多个参数,但是最后一个参数一定为NULL
	例程:
	*/
		execl("/mnt/hgfs/my/system_io/fork_整理/fork_wait", "proecss_name","arg", NULL);



/*		
2、函数原型:
	int execv(const char *path, char *const argv[]);
	
	execv(f1,f2)
	f1:需要执行的文件(命令好像不行,是小编的姿势不对吗?)
	f1:数组
	说明:
	说明:
	f1:需要跳转执行的文件
	f2:
	  第一个元素:文件执行的名字或者别名(执行过程可以用ps -aux查看)
	  第二、三...个元素:需要传入的参数
	  最后一个参数:NULL
	  数组的最后一个元素必须为NULL
 
例程:
*/
		char * argv[] = {"fo","lkkjh", NULL};
		execv("/mnt/hgfs/my/system_io/fork_wait",argv);




/*
3、函数原型:
int execlp(const char *file, const char *arg, ...);

	execlp(f1,f2,f3,f4,...)
	f1:需要使用的命令(系统环境变量的PATH)
	f2、f3、f4....:都是需要依次传入的参数
	注意点:虽然f1表明了需要使用的命令,如ls
		   但是f2仍需要再写一遍
		   不要给自己挖坑,按照规则来
  */  
		execlp("ls", "ls","-al","fork_wait","test1", NULL);



/*
4、函数原型:
int execvp(const char *file, char *const argv[]);

	execvp(f1,f2)
	f1:需要执行的文件或者命令
	f1:数组
	
	文件例程:
	char * argv[] = {"fo","lkkjh", NULL};
	execvp("/mnt/hgfs/my/system_io/fork_整理/fork_wait",argv);
	说明:
	  数组的最后一个元素必须为NULL
	
	命令例程:
	char * argvvv[] = {"ls","-al","test1","test2", NULL};
	execvp("ls",argvvv);
	说明:
	  虽然f1表明了需要使用的命令,如ls
	  但是数组里面仍需要再写一遍,并且在数组的第一个元素
	  不要给自己挖坑,按照规则来
*/
//文件例程:
		char * argv[] = {"fo","lkkjh", NULL};
		execvp("/mnt/hgfs/my/system_io/fork_整理/fork_wait",argv);

//命令例程:
		char * argv[] = {"ls","-al","test1","test2", NULL};
		execvp("ls",argv);



		exit(EXIT_FAILURE);
	}
	printf("exec end\n");

	return 0;
}

其他说明:
exec函数族成功执行后,原有的程序代码都被指定的文件或脚本覆盖,因此这些函数一旦成功后面的代码是无法执行的,他们是无法返回的。


pid_t vfork(void);//进程复刻

#include <sys/types.h>
#include <unistd.h>

pid_t vfork(void);

函数功能:

  • 专门为了exec系列函数服务的一个创建进程的函数,功能跟参数与fork是一模一样,只有一下操作是不一样的
    –vfork成功创建子进程,子进程跑起来,引用的内存是父进程的内存
    –vfork成功创建子进程之后,父进程陷入睡眠
    – 当vfork创建出来的子进程调用了exec系列函数,去加载第三方程序的时候或者是子进程结束,父进程才会被唤醒


注意:

  • vfork这样的操作是为了节约fork函数创建子进程而进行的内存拷贝这一环节的时间(vfork子进程是不会拷贝父进程的虚拟内存的内容)

Daemon进程(翻译:精灵进程、后台进程、守护进程)

概念:
它们常常在系统引导装入时起动,也可以在任意的时刻由开发人员启动,在系统关闭时终止。因为它们没有控制终端,所以说它们是在后台运行的。 Linux 系统有很多精灵进程,它们执行日常事物活动。可以通过命令 ps -ef 来查看那些名字最后一个字母是d进程。

  • 在后台默默的运行
  • 不依赖于所谓的命令终端
  • 本身没有什么外界的限制

Daemon进程创建步骤:

  1. 忽略由于终端关闭而产生的 SIGHUP 信号:
signal( SIGHUP, SIG_IGN );
  1. 为了能正常调用setsid,产生一个非组长子进程,让子进程去继续。
pid_t a = fork();
if(a > 0)
    exit(0);
  1. 创建新会话,抛弃原有的带控制终端的会话。
pid_t a = fork();
if(a > 0)
    exit(0);
  1. 为了防止新建会话的进程再次打开控制终端,让其再产生一个孙子进程,那么这个孙子进程就不是该会话的创始人,因此孙子进程无权也无法打开控制终端。
pid_t a = fork();
if(a > 0)
    exit(0);
  1. 虽然一路创建子孙进程、并创建了新会话,但目前这个孙子进程依然处于原来的进程组中,而进程组可以接收信号,因此这个要升仙的孙子必须脱离原来的进程组,自立门户。
setpgrp();
  1. 关闭释放来自祖辈的所有文件资源
int maxfd = sysconf(_SC_OPEN_MAX); // 返回当前进程打开文件的最大描述符
for(int i=0; i<maxfd; i++)
{
    close(i); // 关闭所有文件(包括打开了的和未打开的)
}
  1. 去除创建文件时影响文件权限的掩码,以便于这位精灵进程创建文件更加灵活
umask(0);
  1. 将当前工作路径,切换到一个不可被卸载的分区下,否则一旦工作路径被卸载,神仙也会死。
chdir("/");

例程——默默在后台向系统日志写入数据:
daemon.c

#include <stdio.h>
#include <unistd.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int daemon_init(void)
{
	int max_fd;
	int i;
	pid_t pid;
	
	/*1,先忽略终端的挂断信号对这个程序的影响*/
	signal(SIGHUP, SIG_IGN);

	/*2,新建一个子进程,让父进程退出,这个时候子进程就不会接收到控制终端的信号跟内容*/
	pid = fork();
	if(pid > 0)
		exit(0);

	/*3,新建一个会话,脱离开原本的会话(也就是这个控制终端)*/
	setsid();

	/*下面的动作都是为了让我们的程序更加的纯净*/

	/*4,新建多一个子进程,脱离开会话管理的权限*/
	pid = fork();
	if(pid > 0)
		exit(0);

	/*5,再让这个子进程脱离开原本的进程组*/
	setpgrp();


	/*6,关闭掉原本的所有文件描述符*/
	max_fd = sysconf(_SC_OPEN_MAX);
	for(i=0; i<max_fd; i++)
		close(i);

	/*7,改变工作路径到根目录*/
	chdir("/");

	/*8,改变原有的掩码,成为没有任何权限影响的0*/
	umask(0);
	
	return 0;
}

int main(void)
{
	daemon_init();	//变成精灵进程
 
	openlog("daemon_test", LOG_CONS | LOG_PID, LOG_DAEMON);//打开系统日志
	
	while(1)
	{
		syslog(LOG_DAEMON, "I am a daemonAAA!");//向系统日志写入数据
		sleep(1);
	}
	closelog();//关闭系统日志
	return 0;
}

结果:
在这里插入图片描述


系统日志相关API

系统日志概念
系统日志是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因,或者寻找受到攻击时攻击者留下的痕迹。系统日志包括系统日志、应用程序日志和安全日志。

void openlog(const char *ident, int option, int facility);//打开系统日志

#include <syslog.h>

void openlog(const char *ident, int option, int facility);

函数功能

  • openlog函数发起到系统日志服务器的连接

函数参数:

  • ident:打开日志文件的时候,跟日志文件说你是谁
    – ident是要向每个消息加入的字符串,典型的情况是要设置成程序的名称。

  • option:是下面一个或多个值的“或”
    –LOG_CONS 如果系统日志服务器不能用,写入控制台
    –LOG_NDELAY 立即打开连接,正常情况下,直到发送第一条消息才打开连接
    –LOG_PERROR 打印输出到stderr
    –LOG_PID 每条消息中包含进程 PID



  • facitity:指定程序发送消息的类型
    –LOG_AUTHPRIV 安全授权消息
    –LOG_CRON 时钟守护进程:cron和at
    –LOG_DAEMON 其他系统守护进程
    –LOG_KERN 内核消息
    –LOG_LPR 打印机子系统
    –LOG_MAIL 邮件子系统
    –LOG_USER 默认






  • priority:指定消息的重要性。
    –LOG_EMERG 系统不能使用
    –LOG_ALERT 立即采取措施
    –LOG_CRIT 紧急事件
    –LOG_ERR 出错条件
    –LOG_WARNING 警告条件
    –LOG_NOTICE 正常但重大事件
    –LOG_INFO 信息消息
    –LOG_DEBUG 调试信息







注意:

  • 严格的说,openlog和closelog是可选的,因为函数syslog在首次使用的时候自动打开日志文件。
  • linux系统上日志文件通常是/var/log/syslog。

例程:

#include <stdio.h>
#include <syslog.h>

int main(void)
{
    syslog(LOG_INFO, "my daemin is OK");
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!