memcached守护进程创建函数位于daemon.c中,创建方式同其它守护进程相同,这里只是作为备忘,特此设置转载。
首先引用《Linux 守护进程 daemon》中守护进程的创建过程,然后附上memcached的daemon.c源码说明。
Linux 守护进程编程
守护进程最重要的特性是后台运行;其次守护进程必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建模式等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的;最后守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。编写守护进程的步骤:(1)在父进程中执行fork并exit推出;(2)在子进程中调用setsid函数创建新的会话;(3)在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;(4)在子进程中调用umask函数,设置进程的umask为0;(5)在子进程中关闭任何不需要的文件描述符setsid函数#include<unistd.h>pid_tsetsid(void);返回值:成功返回新会话的ID;失败返回-1setsid函数创建一个新会话和新进程组,setsid调用保证新会话没有控制终端。守护进程调用该函数将成为新会话的会话领导和新进程组的进程组领导。chdir函数#include<unistd.h>intchdir(const char *path);chdir函数改变当前的工作目录为path所包含的新目录umask函数#includevoidumask(int mask); // r-4; w-2; x-1umask函数改变目录和文件的创建模式。
下面是memcached中的源码及说明:
// create daemon process int daemonize(int nochdir, int noclose) { int fd; // 调用fork产生一个子进程,同时父进程退出。我们所有后续工作都在子进程中完成。 // 这样做我们可以:如果我们是从命令行执行的该程序,这可以造成程序执行完毕的假象,shell会返回 // 刚刚通过fork产生的新进程一定不会是一个进程组的组长,这为第2步的执行提供了前提保障。 // 这样做还会出现一种很有趣的现象:由于父进程已经先于子进程退出,会造成子进程没有父进程, // 变成一个孤儿进程(orphan)。每当系统发现一个孤儿进程,就会自动由1号进程收养它,这样 // 原先的子进程就会变成1号进程的子进程。 switch (fork()) { case -1: return (-1); case 0: break; default: _exit(EXIT_SUCCESS); } // 调用setsid系统调用。这是整个过程中最重要的一步, // 它的作用是创建一个新的会话(session),并自任该会话的组长(session leader) // 如果调用进程是一个进程组的组长,调用就会失败,但这已经在第1步得到了保证。 // 调用setsid有3个作用: // 让进程摆脱原会话的控制; // 让进程摆脱原进程组的控制; // 让进程摆脱原控制终端的控制; // 总之,就是让进程完全独立出来,脱离所有其他进程的控制 if (setsid() == -1) // setsid - run a program in a new session return (-1); /* 把当前工作目录切换到根目录。如果我们是在一个临时加载的文件系统上执行这个进程的, 比如:/mnt/floppy/,该进程的当前工作目录就会是/mnt/floppy/。在整个进程运行期间 该文件系统都无法被卸下(umount),而无论我们是否在使用这个文件系统,这会给我们 带来很多不便。解决的方法是使用chdir系统调用把当前工作目录变为根目录,应该不会有 人想把根目录卸下吧。 当然,在这一步里,如果有特殊的需要,我们也可以把当前工作目录换成其他的路径,比如/tmp */ if (nochdir == 0) { if(chdir("/") != 0) { perror("chdir"); return (-1); } } /* 将文件权限掩码设为0。这需要调用系统调用umask,每个进程都会从父进程那里继承 一个文件权限掩码,当创建新文件时,这个掩码被用于设定文件的默认访问权限,屏蔽掉某些权限, 如一般用户的写权限。当另一个进程用exec调用我们编写的daemon程序时,由于我们不知道那个进程 的文件权限掩码是什么,这样在我们创建新文件时,就会带来一些麻烦。所以,我们应该重新设置文 件权限掩码,我们可以设成任何我们想要的值,但一般情况下,大家都把它设为0,这样,它就不会 屏蔽用户的任何操作。 如果你的应用程序根本就不涉及创建新文件或是文件访问权限的设定,你也完全可以把文件权限 掩码一脚踢开,跳过这一步。关闭所有不需要的文件。同文件权限掩码一样,我们的新进程会从父 进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不被我们的daemon进程读或写, 但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。需要指出的是,文件描述符为0、1和2 的三个文件,也就是我们常说的输入、输出和报错这三个文件也需要被关闭。 很可能不少读者会对此感到奇怪,难道我们不需要输入输出吗?但事实是,在上面的第2步 后,我们的daemon进程已经与所属的控制终端失去了联系,我们从终端输入的字符不可能达到daemon进程, daemon进程用常规的方法(如printf)输出的字符也不可能在我们的终端上显示出来。所以这三个文件已经 失去了存在的价值,也应该被关闭。 */ // 设置文件权限掩码 // umask(0) ; // 这里原本没有,只是为了上面的基本流程说明加上的 if (noclose == 0 && (fd = open("/dev/null", O_RDWR, 0)) != -1) { if(dup2(fd, STDIN_FILENO) < 0) { // 重定向, dup, dup2 - duplicate a file descriptor perror("dup2 stdin"); // int dup2(int oldfd, int newfd); // dup2() makes newfd be the copy of oldfd, closing newfd first if necessary return (-1); } if(dup2(fd, STDOUT_FILENO) < 0) { perror("dup2 stdout"); return (-1); } if(dup2(fd, STDERR_FILENO) < 0) { perror("dup2 stderr"); return (-1); } if (fd > STDERR_FILENO) { if(close(fd) < 0) { perror("close"); return (-1); } } } return (0); }