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