转载:http://www.cnblogs.com/mickole/p/3188321.html
守护进程是生存期长的一种进程,在系统引导时启动,系统关闭时才终止。
Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。
一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。
守护进程的名称通常以d结尾,比如sshd、xinetd、crond等。
首先我们要了解一些基本概念:
进程组 :
会话期:
会话期(session)是一个或多个进程组的集合。
setsid()函数可以建立一个对话期:
setsid()说明:进程从它的双亲进程获得它的对话过程和进程组识别号。setsid()就是将进程和它当前的对话过程和进程组分离开,并且把它设置成一个新的对话过程的领头进程。如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。
(1)此进程变成该对话期的首进程
(2)此进程变成一个新进程组的组长进程。
(3)此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,此函数返回错误。
(4)为了保证这一点,我们先调用fork()然后exit(),此时只有子进程在运行
编写守护进程的一般步骤步骤:
说明:
1. 在后台运行。
为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。
if(pid=fork())
exit(0);//是父进程,结束父进程,子进程继续
setsid()
; 说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
for(i=0;i 关闭打开的文件描述符close(i);
/tmp
,chdir("/")
setsid的使用:
#include
pid_t setsid(void);
以下程序是创建一个守护进程,然后利用这个守护进程每个一分钟向daemon.log文件中写入当前时间:
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while(0);\
void create_deamon(void);
int main()
{
time_t t;
int fd;
create_deamon();
while(1)
{
fd = open("deamon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd == -1)
{
ERR_EXIT("open error!");
}
t = time(0);
char* buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
close(fd);
sleep(60);
}
return 0;
}
void create_deamon()
{
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error!");
if (pid > 0)
exit(EXIT_SUCCESS);
if (setsid() == -1)
ERR_EXIT("setsid() error");
chdir("/");
for (int i = 0; i < 3; ++i)
{
close(i);
open("dev/null", O_RDWR);
dup(0);
dup(0);
}
umask(0);
return;
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch13Deamon$ vim CreateDeamon.c
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch13Deamon$ gcc CreateDeamon.c
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch13Deamon$ ./a.out
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch13Deamon$ ps -ef|grep a.out
ubuntu 7792 6797 0 09:38 pts/0 00:00:00 grep --color=auto a.out
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch13Deamon$ su root
Password:
root@VM-188-113-ubuntu:/home/ubuntu/Code/apue/ch13Deamon# ./a.out
root@VM-188-113-ubuntu:/home/ubuntu/Code/apue/ch13Deamon# ps -ef|grep a.out
root 8082 1 0 09:43 ? 00:00:00 ./a.out
root 8086 8067 0 09:43 pts/0 00:00:00 grep --color=auto a.out
root@VM-188-113-ubuntu:/home/ubuntu/Code/apue/ch13Deamon# cd /
root@VM-188-113-ubuntu:/# ls -l deamon.log
-rw-r--r-- 1 root root 25 Oct 29 09:43 deamon.log
root@VM-188-113-ubuntu:/# cat deamon.log
Sat Oct 29 09:43:06 2016
root@VM-188-113-ubuntu:/# cat deamon.log
Sat Oct 29 09:43:06 2016
Sat Oct 29 09:44:06 2016
结果显示:当我一普通用户执行a.out时,进程表中并没有出现新创建的守护进程,但当我以root用户执行时,成功了,并在/目录下创建了daemon.log文件,cat查看后确实每个一分钟写入一次。为什么只能root执行,那是因为当我们创建守护进程时,已经将当前目录切换我/目录,所以当我之后创建daemon.log文件是其实是在/目录下,那肯定不行,因为普通用户没有权限,或许你会问那为啥没报错呢?其实是有出错,只不过我们在创建守护进程时已经将标准输入关闭并重定向到/dev/null,所以看不到错误信息。
daemon()
创建守护进程#include
int daemon(int nochdir, int noclose);
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while(0);\
int main()
{
time_t t;
int fd;
if (daemon(0, 0) == -1)
ERR_EXIT("daemon error!");
while(1)
{
fd = open("deamon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd == -1)
{
ERR_EXIT("open error!");
}
t = time(0);
char* buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
close(fd);
sleep(60);
}
return 0;
}
守护进程存在的一个问题是如何处理出错消息。因为它本就不应该有控制终端,所以不能只是简单的写到标准错误上。我们不希望所有守护进程都写到控制台设备上,因为在很多的工作站上,控制台设备都运行着一个窗口系统。我们也不希望每个守护进程将它自己的出错信息写到一个单独的文件中。对任何一个系统管理人员而言,如果关系哪一个守护进程写到哪一个记录文件中并定期地检查这些文件,那么一定会使他感到头痛。故需要一个集中的守护进程出错记录设施。
三种产生日志消息的方法:
使用syslogd
守护进程可以读取所有3种格式的日志文件
#include
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}\
while(0);\
int daemonize()
{
int fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
umask(0);
if ((pid = fork()) <0)
{
ERR_EXIT("fork error!");
}
else if (pid != 0)
exit(0);
setsid();
if (chdir("/") < 0)
ERR_EXIT("can't change director to /");
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
ERR_EXIT("can't get file limit.");
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for (unsigned i = 0; i < rl.rlim_max; ++i)
close(i);
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
return 0;
}
int main()
{
daemonize();
openlog("daemotest", LOG_PID, LOG_USER);
syslog(LOG_INFO, "program started.");
while(1)
{
sleep(1);
}
return 0;
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch13DaemonProcesses$ gcc UseSyslog.c -o daemotestubuntu@VM-188-113-ubuntu:~/Code/apue/ch13DaemonProcesses$ ./daemotest
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch13DaemonProcesses$ ps axj | grep daemotest
1 20370 20370 20370 ? -1 Ss 500 0:00 ./daemotest
19411 20382 20381 19411 pts/2 20381 S+ 500 0:00 grep --color=auto daemotest
可以看出daemotest进程的父进程是1,没有终端控制(TTY选项为“?”)。其记录在/var/log/messages文件中,可以在该文件中找到”program started”字样的记录。