时隔多年,每次碰到守护进程就想起当年,当年在大学学linux的时候,需要做个大作业,然后老师给了好多个题目,翻来翻去,发现就这个守护进程最简单,那就选守护进程吧。
选了守护进程的题目之后,发现还是不会做(哎,当年就没想过做linux相关的,真是人算不如天算)。不会做怎么办呢?那就找同学借鉴了(说是借鉴其实就是抄),然后就找到了叶某人的抄了过来,好像当时是完全抄过来的,因为当年确实对守护进程很懵逼。抄完了,那就交作业了。
交了作业,就开始答辩了,我们组是在前面答辩,答辩说了一些,也忘记具体说啥了,反正最后的评分,比抄叶某人的评分高了很多,抄的人分数反而更高,那时候嘲笑了叶某人好久,哈哈哈。
回忆总是美好的,现在该卷还是要卷,开始我们今天的守护进程之旅吧。
12.1 前后台进程其实前后台进程,不应该放在这里讲的,不过都安排在这里了,就在这里吧,这样也好区别守护进程。
12.1.1 前台进程先来看看前台进程,前台进程很简单就是运行在前台的,绑定了控制终端的,可以接受控制终端的信号。
我们来写一个前台进程:
#includeint main() { printf("testn"); while(1); return 0; }
当然真正的代码不能像我这样写,这是一个测试代码,我们接着来编译运行:
root@ubuntu:~/c_test/12# gcc test.c -o test root@ubuntu:~/c_test/12# ./test test ls # 输入ls没有反应 ls # 输入ls没有反应 ^C # 输入中断键,退出了 root@ubuntu:~/c_test/12#
这种就是前台进程,我们再来看看属性:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 1415 1513 1513 1415 pts/0 1513 R+ 0 0:09 ./test
R+:表示正在运行的进程,+是前台进程
TTY:就是控制终端,pts就是网络终端
TPGID:进程连接到的tty(终端)所在的前台进程组的ID
12.1.2 后台进程接着我们来看看后台进程,后台进程其实也比较简单,启动的时候加一个&,就可以了
root@ubuntu:~/c_test/12# ./test & [1] 1571 test root@ubuntu:~/c_test/12#
接着我们来看看状态:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 1415 1571 1571 1415 pts/0 1415 R 0 0:19 ./test
STAT:后面没有+,说明不是前台进程。
TTY:但是终端还是pts/0
但是我们通过终端去发送中断信号,后台进程是接收不到的,只能是关闭终端,终端都关闭了,终端下的进程自然都会关闭。
这个实验,只能自己看了。
12.1.3 nohup命令在上家公司工作的时候,就发现了这个命令来启动后端程序,现在我们来看看:
root@ubuntu:~/c_test/12# nohup ./test & [1] 1673 root@ubuntu:~/c_test/12# nohup: ignoring input and appending output to 'nohup.out' root@ubuntu:~/c_test/12# ls nohup.out test test.c root@ubuntu:~/c_test/12# cat nohup.out
好像有那么点像模像样,我们查看一下状态:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 1653 1673 1673 1653 pts/0 1653 R 0 0:13 ./test
好像还是有控制终端,我们把控制终端关闭了测试一下。
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 1 1673 1673 1653 ? -1 R 0 5:42 ./test
好神奇哦,关闭了终端,TTY也跟着改了,这样看着就是真正的守护进程了。
nohup命令做了一下事情:
- 阻止SIGHUP信号发到这个进程。
- 关闭标准输入。该进程不再能够接收任何输入,即使运行在前台。
- 重定向标准输出和标准错误到文件nohup.out。
这一篇文章不错:[Linux 守护进程的启动方法]
12.2 守护进程上面吹了这么多水,终于来到了今天的重点,守护进程的实现,我们在代码中实现守护进程,启动的的时候不用带nohup和&,直接./就可以了。
12.2.1 守护进程步骤-
创建子进程,父进程退出(必须的)
有如下原因:
第一:父进程有可能是进程组组长(在命令行启动下是肯定的),从而不能够执行后面的setsid函数,子进程继承了父进程的进程组ID,所以子进程一定不是进程组组长,所以子进程一定可以执行setsid。
第二:父进程的退出,shell会以为这条命令执行结束了,从而让子进程在后台执行,也就是变成孤儿进程。
-
在子进程创建新会话(必须)
这一步是调用setsid函数,也是关键的一步,这一步是脱离了终端,因此终端发送的信号,都不会影响到子进程。子进程调用这个函数之后,会成为新会话的首进程,成为一个新进程组的组长进程,没有控制终端
-
修改当前目录为根目录(不是必须)
只有根目录是一定存在的,如果是其他目录,有可能存在卸载等问题,所以可以修改成根目录。chdir("/")
-
重设文件权限掩码(不是必须)
文件权限掩码是继承父进程的,有可能父进程的权限有点低,所以为了增加守护进程的灵活性,需要重新设置文件掩码。umask(0)。
-
再次fork,父进程退出(不是必须)
daemon可能会打开一个终端设备,这个打开终端设备可能会成为daemon进程的控制终端。既然如此,为了确保万无一失,只有确保daemon不是会话首进程,所以需要再次fork。
-
关闭文件描述符(不是必须)
文件描述符也是继承自父进程的,在守护进程中是不需要这些的,所以都需要关闭。标准输入,标准输出,标准错误这些都不需要,统一关闭。
先按照上面的步骤写一波守护进程的实现
#include#include #include #include void daemonize() { pid_t pid = -1; struct rlimit rl; // 获取父进程打开文件 if(getrlimit(RLIMIT_NOFILE, &rl) < 0) printf("getrlimit errn"); // 1.fork,父进程退出 pid = fork(); if(pid < 0) { printf("fork errn"); return 0; } else if(pid > 0) { //父进程退出 _exit(0); } // 下面都是子进程 // 2.设置新会话 setsid // 创建一个新会话,并且是进程组的组长 setsid(); // 3.设置根目录 chdir chdir("/"); // 4.设置文件掩码 umask umask(0); // 5.再次fork,防止创建一个新的终端 if((pid = fork()) < 0) { printf("fork errn"); return 0; } else if(pid > 0) { _exit(0); } // 6.关闭标准输入,标准输出,标准错误 if(rl.rlim_max == RLIM_INFINITY) rl.rlim_max = 1024; for(int i=0; i 就这样吧。
12.2.3 守护进程的进化版有些守护进程需要做单实例,可以使用文件和记录锁来做单实例,第一次启动守护进程,就会创建文件,并且加一把写锁,之后如果再有守护进程创建,再去写这个文件,就会失败,从而做到了单实例。
还有守护进程有以下的惯例:
- 若守护进程使用锁文件,那么该文件通常存储在/var/run目录中,需要超级用户权限才能在此目录下创建文件,锁文件名字通常是name.pid
- 若守护进程支持配置选项,那么配置文件通常存放在/etc目录中,配置文件名字为name.conf
- 守护进程可用命令行启动,但是也可以在系统初始化脚本启动。(/etc/rc*或/etc/init.d __close_nocancel_nostatus (fd); __set_errno (ENODEV); return -1; } } else { __close_nocancel_nostatus (fd); return -1; } } return (0); }
其实看这个源码跟我们上面讲的也差不多,只不多这是有两个参数。
nochdir:用来控制是否将当前目录切换到根目录。(看代码也理解了)
noclose :用来控制是否将标准输入,标准输出,标准错误重定向到/dev/null。(这个看代码也理解)
12.3 总结虽然这篇守护进程的代码没有写全,但是原理也都介绍了,具体到项目的时候,在把守护进程写全,因为服务器的代码基本都是守护进程的方式存在的,不急,未来的路还很长。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)