linux 进程2
一. exec族函数
1.1. 为什么需要exec函数
a. fork子进程是为了执行新程序(fork创建了子进程后,子进程和父进程同时被OS调度执行,因此子进程可以单独的执行一个程序,这个程序宏观上将会和父进程程序同时进行)
b. 使用exec族运行新的可执行程序(exec族函数可以直接把一个编译好的可执行程序直接加载运行)
c. 我们有了exec族函数后,我们典型的父子进程程序是这样的:子进程需要运行的程序被单独编写、单独编译连接成一个可执行程序(叫hello),(项目是一个多进程项目)主程序为父进程,fork创建了子进程后在子进程中exec来执行hello,达到父子进程分别做不同程序同时(宏观上)运行的效果。
1.2. 函数介绍
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
a. execl和execv 这两个函数是最基本的exec,都可以用来执行一个程序,区别是传参的格式不同。execl是把参数列表(本质上是多个字符串,必须以NULL结尾)依次排列而成(l其实就是list的缩写),execv是把参数列表事先放入一个字符串数组中,再把这个字符串数组传给execv函数。
b. execlp和execvp 这两个函数在上面2个基础上加了p,较上面2个来说,区别是:上面2个执行程序时必须指定可执行程序的全路径(如果exec没有找到path这个文件则直接报错),而加了p的传递的可以是file(也可以是path,只不过兼容了file。加了p的这两个函数会首先去找file,如果找到则执行执行,如果没找到则会去环境变量PATH所指定的目录下去找,如果找到则执行如果没找到则报错)
c. execle和execvpe 这两个函数较基本exec来说加了e,函数的参数列表中也多了一个字符串数组envp形参,e就是environment环境变量的意思,和基本版本的exec的区别就是:执行可执行程序时会多传一个环境变量的字符串数组给待执行的程序。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1.3. main函数的第三个
a. main函数的原型其实不止是int main(int argc, char **argv),而可以是int main(int argc, char **argv, char **env) 第三个参数是一个字符串数组,内容是环境变量。
b. 如果用户在执行这个程序时没有传递第三个参数,则程序会自动从父进程继承一份环境变量(默认的,最早来源于OS中的环境变量);如果我们exec的时候使用execlp或者execvpe去给传一个envp数组,则程序中的实际环境变量是我们传递的这一份(取代了默认的从父进程继承来的那一份)
二. 进程状态和system函数
2.1. 进程的5种状态
a. 就绪态。这个进程当前所有运行条件就绪,只要得到了CPU时间就能直接运行。
b. 运行态。就绪态时得到了CPU就进入运行态开始运行。
c. 僵尸态。进程已经结束但是父进程还没来得及回收
d. 等待态(浅度睡眠&深度睡眠),进程在等待某种条件,条件成熟后可进入就绪态。等待态下就算你给他CPU调度进程也无法执行。浅度睡眠等待时进程可以被(信号)唤醒,而深度睡眠等待时不能被唤醒只能等待的条件到了才能结束睡眠状态。
e. 暂停态。暂停并不是进程的终止,只是被被人(信号)暂停了,还可以回复的。
2.2. system函数
2.2.1. 函数源码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include #include #include #include int system(const char * cmdstring) { pid_t pid; int status; if(cmdstring == NULL){ return (1); } if((pid = fork())<0){ status = -1; } else if(pid == 0){ execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); -exit(127); //子进程正常执行则不会执行此语句 } else{ while(waitpid(pid, &status, 0) < 0){ if(errno != EINTER){ status = -1; break; } } } return status; }
2.2.2. Linux下使用system函数一定要谨慎
三. 进程关系划分
3.1. 父子进程关系
a. 上述已介绍
3.2. 进程组(group)
a. process group(进程组)是一组相关联的进程,用来方便信号量的分发
3.3. 会话(session)
a. session(回话)是用户登录系统以后所需的context(上下文)
b. session退出以后所有隶属于该session的进程组都会收到hup信号而挂起,这样就有了控制进程生命周期的作用
四. 守护进程
4.1. 什么是守护进程
4.1.1. 何谓守护进程-后台运行
4.1.2. daemon,表示守护进程,简称为d(进程名后面带d的基本就是守护进程)
4.1.3. 与控制台脱离(普通进程都和运行该进程的控制台相绑定,表现为如果终端被强制关闭了则这个终端中运行的所有进程都被会关闭,背后的问题还在于会话)
4.1.4. 服务器(Server),服务器程序就是一个一直在运行的程序,可以给我们提供某种服务(譬如nfs服务器给我们提供nfs通信方式),当我们程序需要这种服务时我们可以调用服务器程序(和服务器程序通信以得到服务器程序的帮助)来进程这种服务操作。服务器程序一般都实现为守护进程。
4.2. 关闭守护进程
4.2.1. 由于守护进程与控制台无关,故不能通过关闭控制台关闭
4.2.2. 向进程发送信号指令kill
a. kill -信号编号 进程ID,向一个进程发送一个信号
b. kill -9 xxx,将向xxx这个进程发送9号信号,也就是要结束进程
4.3. 常见守护进程
a. syslogd,系统日志守护进程,提供syslog功能。
b. cron,cron进程用来实现操作系统的时间管理,linux中实现定时执行程序的功能就要用到cron。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
root@ubuntu:/mnt/hgfs/windows_share/baseC/APPNet# ps -aux | grep "syslogd" syslog 682 0.0 0.0 31372 844 ? Ssl Mar11 0:01 rsyslogd root@ubuntu:/mnt/hgfs/windows_share/baseC/APPNet#
4.4. 编写简单守护进程
4.4.1. 任何一个进程都可以将自己实现成守护进程
4.4.2. create_daemon函数的使用
a. 子进程等待父进程退出
b. 子进程使用setsid创建新的会话期,脱离控制台
c. 调用chdir将当前工作目录设置为/
d. umask设置为0以取消任何文件权限屏蔽
e. 关闭所有文件描述符
f. 将0、1、2定位到/dev/null
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> void create_daemon(void); int main(void) { create_daemon(); while (1) { printf("I am running.\n"); sleep(1); } return 0; } // 函数作用就是把调用该函数的进程变成一个守护进程 void create_daemon(void) { pid_t pid = 0; pid = fork(); if (pid < 0) { perror("fork"); exit(-1); } if (pid > 0) { exit(0); // 父进程直接退出 } usleep(100); // 执行到这里就是子进程 // setsid将当前进程设置为一个新的会话期session,目的就是让当前进程 // 脱离控制台。 pid = setsid(); if (pid < 0) { perror("setsid"); exit(-1); } // 将当前进程工作目录设置为根目录 chdir("/"); // umask设置为0确保将来进程有最大的文件操作权限 umask(0); // 关闭所有文件描述符 // 先要获取当前系统中所允许打开的最大文件描述符数目 int cnt = sysconf(_SC_OPEN_MAX); int i = 0; for (i=0; i<cnt; i++) { close(i); } open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); }
4.5. 使用syslog来记录调试信息
4.5.1. openlog、syslog、closelog的使用
4.5.2. 一般log信息都在操作系统的/var/log/messages这个文件中存储着,但是ubuntu中是在/var/log/syslog文件中的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <syslog.h> #include <sys/types.h> #include <unistd.h> int main(void) { printf("my pid = %d.\n", getpid()); openlog("b.out", LOG_PID | LOG_CONS, LOG_USER); syslog(LOG_INFO, "this is my log info.%d", 23); syslog(LOG_INFO, "this is another log info."); syslog(LOG_INFO, "this is 3th log info."); closelog(); }