守护进程
守护进程时指在后台运行的、没有控制终端与之相连的进程。他独立于控制终端,通常周期性地执行某种任务。Linux大多数服务器就是用守护进程方式实现的。
实现方法
- 让程序在后台运行。
进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 - 脱离控制终端,登录会话和进程组 setsid()
当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 - 禁止进程重新打开控制终端
进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端。 - 关闭不再需要的文件描述符
进程不再需要文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们: for(i=0;i 关闭打开的文件描述符close(i); - 改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。 - 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0); 处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。简单实现代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <time.h>
#include <syslog.h>
int init_daemon(void) {
int pid;
int i;
/*忽略终端I/O信号,STOP信号*/
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP,SIG_IGN);
pid = fork();
if(pid > 0) {
exit(0); /*结束父进程,使得子进程成为后台进程*/
} else if(pid < 0) {
return -1;
}
/*创建一个新的进程组,在这个新的组中,子进程成为进程组的首进程,以使该进程脱离所有终端*/
setsid();
/*再创建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端*/
pid = fork();
if(pid > 0) {
exit(0);
} else if(pid < 0) {
return -1;
}
/*关闭所有从父进程继承的不再需要的文件描述符*/
for(i = 0;i < NOFILE;close(i++));
/*改变工作目录,使得进程不与任何文件系统联系*/
chdir("/");
/*将文件屏蔽字设置为0*/
umask(0);
/*忽略SIGCHLD信号*/
signal(SIGCHLD,SIG_IGN);
return 0;
}
int main(int argc, char *argv[]) {
time_t now;
init_daemon();
syslog(LOG_USER|LOG_INFO,"测试守护进程!");
while(1) {
sleep(8);
time(&now);
syslog(LOG_USER|LOG_INFO,"系统时间:\t%s\t\t\n",ctime(&now));
}
return 0;
}
fork和vfork的区别
相同
- 都可以用来创建一个新进程
- 都是调用一次,返回两次
不同
- fork创建一个子进程的时候,子进程只是完整复制子进程的资源。这样得到的子进程独立于父进程,具有良好的并发性。而使用vfork创建子进程时,操作系统并不将父进程的地址空间完全复制到子进程,用vfork创建的子进程共享父进程的地址空间,也就是说子进程完全运行在父进程的地址空间上。子进程对该地址空间中任何数据的修改同样为父进程所见。
- 使用fork创建一个子进程时,哪个进程先运行取决于系统的调度算法。而vfork一个进程时,vfork保证子进程先运行,当它调用exec或exit后,父进程才可以被调度运行。如果在调用exec或exit之前子进程要依赖父进程的某几个行为,就会导致死锁。
进程退出
正常退出
- 在main函数中执行return
- 调用exit
- 调用_exit
异常退出 - 调用abort函数
2.进程收到某个信号,该信号使进程终止进程收到某个信号,该信号使进程终止
执行新进程
#include<unistd.h>
int exevc(const char *pathname,char *const argv[]);
参数filename字符串所代表的文件路径,argv参数是一个以空指针结尾的数组,即该数组里面存放的是命令执行需要的参数,最后一个元素是NULL 。argv[0]存放名字,而参数在第二个及以后存放
#include<unistd.h>
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
该函数如果执行成功则无返回值,执行失败则直接返回-1,失败原因存于errno 中。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
第一个参数path字符指针所指向要执行的文件路径, 接下来的参数代表执行该文件时传递的参数列表:argv[0],argv[1]… 最后一个参数须用空指针NULL作结束。
该函数如果执行成功则无返回值,执行失败则直接返回-1,失败原因存在errno中。
#include<unistd.h>
int execvp(const char *file ,char * const argv []);
execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个参数argv 传给该欲执行的文件
该函数如果执行成功则无返回值,执行失败则直接返回-1,失败原因存在errno中。
#include<unistd.h>
int execlp(const char * file,const char * arg,....);
execlp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0], argv[1], …, 最后一个参数必须用空指针(NULL)作结束.
该函数如果执行成功则无返回值,执行失败则直接返回-1,失败原因存在errno中。
#include<unistd.h>
int execle(const char * path,const char * arg,....,char *const envp[]);
execl是用来执行参数path字符串所代表的文件路径,并为新程序复制最后一个参数所指示的环境变量。接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
该函数如果执行成功则无返回值,执行失败则直接返回-1,失败原因存在errno中。
等待进程结束
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
返回值:若函数执行成功,则返回得到状态的进程id;若函数执行出错则在大部分情况下返回-1,在指定了一定参数时返回0。
waitpid()可以指定等待哪一个具体的进程结束。
wait();
如果参数的值不是NULL,wait()就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的
waitpid();
参数pid的值有以下几种类型:
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去.
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样.
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬.
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值.
参数options的值有以下几种类型:
如果使用了WNOHANG参数,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去,非阻塞版本.
如果使用了WUNTRACED参数,则子进程进入暂停则马上返回,但结束状态不予以理会.
Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用”|”运算符把它们连接起来使用,如果我们不想使用它们,也可以把options设为0,如:ret=waitpid(-1,NULL,0);
waitpid的返回值比wait稍微复杂一些,一共有3种情况:
a、当正常返回的时候waitpid返回收集到的子进程的进程ID;
b、设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
c、如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD.
获得进程ID
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
获得进程的ID号
获得父进程的ID号
setuid和setgid
#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid); //设置进程实际用户ID
int setgid(gid_t gid); //设置进程实际组ID
改变进程的优先级
#include <unistd.h>
int nice(int increment);
#include <sys/resource.h>
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int prio);
which和who取值的意义:
getpriority
- PRIO_PROCESS 一个特定的进程,此时who的取值为ID进程
- PRIO_PGRP 一个进程组的所有进程,此时who取值为进程组ID
- PRIO_USER 一个用户拥有的所有进程,此时who值为实际用户
- ERSCH which和who的组合与现存的所有进程均不匹配
- EINVAL which是个无效的值
setpriority
- ERERM 要设置优先级的进程与当前的进程不属于同一个用户,并且当前进程没有CAP_SYS_NICE特许
- EACCES 该调用可能降低进程的优先级并且进程没有CAP_SYS_NICE特许