一、守护进程的概念
我们在前面的博文《进程信号的产生》中提到过前台进程和后台进程的概念,它们之间的区别就是是否获得键盘文件,拥有键盘输入的就是前台进程,后台进程获取不到键盘文件
守护进程是 linux 系统中运行在后台的特殊进程,具有无终端依赖、长期运行、权限分离等特点,通常用于实现系统服务、网络服务等功能,进行守护进程化的进程就是将终端关闭也不会杀死,只要服务器一直运行,它就可以一直运行,但可以通过kill
命令杀死
二、调用接口
1、创建新会话
#include <unistd.h> pid_t setsid(void);
返回值:成功返回新会话id,失败返回-1
2、守护进程
#include <unistd.h> int daemon(int nochdir, int noclose);
返回值:成功返回0,失败返回-1
nochdir
:如果该参数的值为 0,daemon
函数会将当前工作目录更改为根目录(/),如果为非 0 值,则不更改当前工作目录noclose
:如果该参数的值为 0,daemon 函数会将标准输入、标准输出和标准错误输出重定向到/dev/null
,如果为非 0 值,则不进行重定向
三、进程属性信息
1. ppid(parent process id)
- 全称:父进程id
- 含义:当前进程的父进程的唯一标识符
- 作用:用于跟踪进程间的父子关系(例如,通过
fork()
创建的子进程会继承父进程的ppid)
2. pid(process id)
- 全称:进程id
- 含义:操作系统分配给每个进程的唯一数字标识符(范围通常为1~32768)
- 作用:用于区分和管理系统中的不同进程(例如,通过
kill
命令终止进程)
3. pgid(process group id)
- 全称:进程组id
- 含义:进程所属的进程组的id,进程组是一个或多个相关进程的集合,一个任务对应一个进程组
- 作用:便于批量管理相关进程(如停止或恢复整个进程组)
4. sid(session id)
- 全称:会话id
- 含义:进程所属的会话的id,会话由一个或多个进程组组成,通常由登录
shell
创建,实际上是bash
的id
- 作用:管理终端会话中的所有进程(例如,当用户注销时,整个会话的进程会被终止)
5. tty(teletypewriter)
- 全称:终端设备
- 含义:进程关联的终端设备(如
pts/0
表示伪终端),若显示为?
,表示进程无控制终端 - 作用:区分进程是否在终端中运行(如后台服务通常无终端)
6. tpgid(foreground process group id)
- 全称:前台进程组id
- 含义:当前在终端前台运行的进程组的id
- 作用:终端输入/输出会被分配给前台进程组(例如,按
ctrl+c
会中断前台进程)
7. stat(process status)
全称:进程状态
含义:进程的当前状态,常见状态码包括:
r
(运行中)s
(睡眠中)d
(不可中断睡眠)t
(停止)z
(僵尸进程)
作用:快速判断进程的运行状态(例如,z
表示进程已终止但未被父进程回收)
8. uid(user id)
- 全称:用户id
- 含义:启动进程的用户的唯一标识符(如
root
的uid为0) - 作用:用于权限控制(例如,普通用户无法修改
root
进程)
9. time
- 全称:cpu时间
- 含义:进程自启动以来累计使用的cpu时间(格式为
hh:mm:ss
) - 作用:衡量进程对cpu资源的占用情况
10. command
- 全称:命令名称
- 含义:启动进程的命令或程序名称(如
bash
、nginx
) - 作用:快速识别进程的功能(例如,通过
command
列判断是否为恶意程序)
同一个session
内启动的sid
都一样
同一pgid
的,即一个组内的,pid
与pgid
相同的被称为组长
四、深入理解守护进程
1、实现过程
首先创建子进程然后退出父进程,使子进程在后台运行,然后创建新会话,脱离掉原会话和控制终端,保证进程不受终端信号影响,然后再重定向标准输入输出,确保守护进程不与终端交互,然后变更工作目录,忽略掉子进程退出信号和终端停止信号,所以守护进程本质上是一个孤儿进程
2、实例
#include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <syslog.h> void daemonize() { // 创建子进程并退出父进程,子进程成为孤儿进程 pid_t pid = fork(); if (pid < 0) exit(exit_failure); if (pid > 0) exit(exit_success); // 创建新会话,调用该函数的进程会成为新会话的会话首进程、新进程组的组长进程 //并且会脱离原有的控制终端 if (setsid() == -1) exit(exit_failure); // 重定向标准输入输出 close(stdin_fileno); close(stdout_fileno); close(stderr_fileno); int fd = open("/dev/null", o_rdwr); dup2(fd, stdin_fileno); dup2(fd, stdout_fileno); dup2(fd, stderr_fileno); // 允许所有权限 umask(0); // 变更工作目录 chdir("/"); // 忽略子进程状态改变时发送给父进程的信号 signal(sigchld, sig_ign); } int main() { daemonize(); while (1) { sleep(3600); // 模拟工作 } return 0; }
上面的过程,可以被函数daemon
函数替换
#include <unistd.h> #include <syslog.h> int main() { // 使用 daemon 函数将当前进程转换为守护进程 // 第一个参数 0 表示将工作目录更改为根目录 // 第二个参数 0 表示将标准输入、标准输出和标准错误输出重定向到 /dev/null if (daemon(0, 0) == -1) { perror("daemon"); return 1; } // 模拟守护进程持续工作 while (1) { // 睡眠 3600 秒(1 小时) sleep(3600); } return 0; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论