Linux操作系统———守护进程

守护进程:

第一步:fork+退出父进程

父进程(你运行的原始程序)立刻fotk()出一个子进程,然后字节退出

为什么?因为shell(你敲命令的窗口)会以为这个程序已经结束了,不会卡住等它

子进程就会变成"孤儿",这就是我们想要的

目的:让进程脱离用户命令行,变成后台运行

第二步:调用setsid()

子进程调用setside(),创建一个全新的会话(session),自己当"会话老大"

这样他就彻底地和原来的终端断开联系了

原来的终端即使关闭(比如你关掉SSH或终端窗口),也不会发出信号(比如SIGHUP)把它干掉

目的:脱离控制终端,避免被用户操作意外杀死

第三步:再fork一次(可选且推荐)

再fork一次生成一个"孙子进程",原来的子进程退出

为什么,因为"会话老大"有资格重新获取一个控制终端(虽然我们不希望这样)

"孙子"不是老大,就永远没机会绑定中断了,更安全

目的:彻底断绝与终端的任何可能联系

第四步:把工作目录改成根目录/(chdir("/"))

比如你是在/home/you/project下启动的程序,如果不改目录,这个目录就不能被卸载

改成 / 后,就不会"占着茅坑不拉屎"

目的:避免阻碍文件系统卸载

第五步:重设文件权限掩码

每个进程都有一个叫umask的东西,它会偷偷"遮掉"你创建文件的某些权限。

比如你写open("log.txt", 0666),但umask是022,最后文件权限变成0644。

调用umask(0)就是说:"别遮,我要完全控制权限"

目的:确保守护进程创建的文件权限是精确可控的。

第六步:关闭文件描述符

守护进程通常不需要标准输入、输出和错误文件描述符,因为它们不与终端交互。关闭这些不需要的文件描述符可以避免资源泄露,提高守护进程的安全性和效率

目的:节省资源,提高安全性

第七步;处理信号

SIGHUP:虽然守护进程和终端断开,但仍然有可能收到其它进程或内核发来的SIGHUP信号,搜狐进程不应该因为它而终止。

SIGTERM:SIGTERM是终止信号,用于请求守护进程优雅地终止。通过命令行执行kill <pid>命令可以发哦是那个SIGTERM信号,接收到这个信号之后,守护进程终止子进程,并清理回收资源,最后退出

目的:既不能被误杀,又能被正常关闭

第八步:执行具体任务

这一步是守护进程的核心,它开始执行为其设计的特定功能,如监听网络请求,定期清理文件系统、执行系统备份等

/**

*@brief 如果调用进程不是进程组的领导者,则创建一个新的会话。创建者是新会话的领导者

*@return pid_t 成功则会返回调用进程的新会话ID,失败则返回(pid_t)-1,并设置errno以指明错误原因

*/
pid_t setsid(void);

/**

*@brief 设置调用进程的文件模式创建掩码

*@param mask 掩码。是一个八进制数,它hiding哪些权限位在文件或目录创建时应被关闭。我们通过umask(0)确保守护进程创建的文件和目录具有最开放的权限设置。

*@return mode_t 这个系统调用必然成功,返回之前的掩码值

*/
mode_t umask(mode_t mask);

/**

*@brief 更改调用进程的工作目录

*@param path 更改后的工作路径

*@return int 成功返回0,失败返回-1,并设置errno

*/
int chdir(const char *path);

------------------------daemon_test.c------------------------

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h> // UNIX标准函数,如fork, setsid, chdir等

#include <sys/types.h>

#include <sys/stat.h> // 文件状态,如umask

#include <sys/wait.h> // 等待子进程

#include <syslog.h>

#include <string.h>

#include <fcntl.h> // 文件控制,如open

#include <signal.h> // 信号处理

#include <string.h>

#include <errno.h> // 错误号

pid_t pid; // 全局变量,用于存储子进程(tcp_server)的pid

int is_shutdown = 0; // 标志位,表示是否收到SIGTERM信号准备退出

void signal_handler(int sig)

{

switch (sig)

{

case SIGHUP:

syslog(LOG_WARNING, "收到SIGHUP信号..."); // 忽略SIGHUP,只记录

break;

case SIGTERM:

syslog(LOG_NOTICE, "接收到终止信号,准备退出守护进程...");

syslog(LOG_NOTICE, "向子进程发送SIGTERM信号...");

is_shutdown = 1; // 设置标志位

kill(pid, SIGTERM); // 向子进程(tcp_server)发送SIGTERM

break;

default:

syslog(LOG_INFO, "Received unhandled signal");

}

}

void my_daemonize()

{

pid_t pid;

// 第一次fork:创建子进程,父进程退出

pid = fork();

if (pid < 0)

exit(EXIT_FAILURE); // fork失败

if (pid > 0)

exit(EXIT_SUCCESS); // 父进程退出,子进程继续

// 创建新会话,脱离终端

if (setsid() < 0)

exit(EXIT_FAILURE);

// 处理 SIGHUP、SIGTERM 信号

signal(SIGHUP, signal_handler);

signal(SIGTERM, signal_handler);

// 第二次fork:确保守护进程不是会话首进程,避免意外获取终端

pid = fork();

if (pid < 0)

exit(EXIT_FAILURE);

if (pid > 0)

exit(EXIT_SUCCESS); // 第一个子进程退出,第二个子进程继续

// 重置文件权限掩码

umask(0);

// 改变工作目录到根目录,避免占用启动目录

chdir("/");

// 关闭所有打开的文件描述符

for (int x = 0; x <= sysconf(_SC_OPEN_MAX); x++)

{

close(x);

}

// 打开系统日志,后续日志将写入系统日志

openlog("this is our daemonize process: ", LOG_PID, LOG_DAEMON);

}

int main()

{

my_daemonize(); // 将当前进程守护化

while (1) // 无限循环,守护进程主循环

{

pid = fork(); // 创建子进程,用于运行tcp_server

if (pid > 0) // 父进程(守护进程)

{

syslog(LOG_INFO, "守护进程正在监听服务端进程...");

waitpid(-1, NULL, 0); // 等待任意子进程退出(阻塞)

if (is_shutdown) {

syslog(LOG_NOTICE, "子进程已被回收,即将关闭syslog连接,守护进程退出");

closelog(); // 关闭日志

exit(EXIT_SUCCESS); // 退出守护进程

}

syslog(LOG_ERR, "服务端进程终止,3s后重启..."); // 如果子进程异常退出,则重启

sleep(3);

}

else if (pid == 0) // 子进程

{

syslog(LOG_INFO, "子进程fork成功");

syslog(LOG_INFO, "启动服务端进程");

char *path = "/home/daemon_and_multiplex/tcp_server"; // 服务器程序路径

char *argv\[\] = {"my_tcp_server", NULL}; // 参数列表,第一个参数是程序名

errno = 0;

execve(path, argv, NULL); // 执行服务器程序

// 如果execve失败,会继续执行下面的代码

char buf1024;

sprintf(buf, "errno: %d", errno);

syslog(LOG_ERR, "%s", buf);

syslog(LOG_ERR, "服务端进程启动失败");

exit(EXIT_FAILURE);

}

else // fork失败

{

syslog(LOG_ERR, "子进程fork失败");

}

}

return EXIT_SUCCESS;

}

---------------------------tcp_server.c------------------------

#include <sys/socket.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <netinet/in.h> // 网络地址结构

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <arpa/inet.h> // 网络地址转换

#include <pthread.h>

#include <unistd.h> // 标准函数

#include <signal.h>

#include <syslog.h> // 系统日志

int sockfd; // 全局变量,服务器socket

void zombie_dealer(int sig)

{

pid_t pid;

int status;

char buf1024;

memset(buf, 0, 1024);

// 一个SIGCHLD可能对应多个子进程的退出

// 使用while循环回收所有退出的子进程,避免僵尸进程的出现

while ((pid = waitpid(-1, &status, WNOHANG)) > 0)

{

if (WIFEXITED(status))

{

sprintf(buf, "子进程: %d 以 %d 状态正常退出,已被回收\n", pid, WEXITSTATUS(status));

syslog(LOG_INFO, "%s", buf);

}

else if (WIFSIGNALED(status))

{

sprintf(buf, "子进程: %d 被 %d 信号杀死,已被回收\n", pid, WTERMSIG(status));

syslog(LOG_INFO, "%s", buf);

}

else

{

sprintf(buf, "子进程: %d 因其它原因退出,已被回收\n", pid);

syslog(LOG_WARNING, "%s", buf);

}

}

}

void sigterm_handler(int sig) {

syslog(LOG_NOTICE, "服务端接收到守护进程发出的SIGTERM,准备退出...");

syslog(LOG_NOTICE, "释放sockfd");

close(sockfd); // 关闭监听socket

syslog(LOG_NOTICE, "释放syslog连接,服务端进程终止");

closelog(); // 关闭日志

// 优雅退出

exit(EXIT_SUCCESS);

}

void read_from_client_then_write(void *argv)

{

int client_fd = *(int *)argv; // 客户端socket

ssize_t count = 0, send_count = 0;

char *read_buf = NULL;

char *write_buf = NULL;

char log_buf1024;

memset(log_buf, 0, 1024);

read_buf = malloc(sizeof(char) * 1024);

// 判断内存是否分配成功

if (!read_buf)

{

sprintf(log_buf, "服务端pid: %d: 读缓存创建异常,断开连接\n", getpid());

syslog(LOG_ERR, "%s", log_buf);

shutdown(client_fd, SHUT_WR); // 关闭写端

close(client_fd);

return;

}

// 判断内存是否分配成功

write_buf = malloc(sizeof(char) * 1024);

if (!write_buf)

{

sprintf(log_buf, "服务端pid: %d: 写缓存创建异常,断开连接\n", getpid());

syslog(LOG_ERR, "%s", log_buf);

free(read_buf); // 释放读缓冲区

shutdown(client_fd, SHUT_WR);

close(client_fd);

return;

}

// 循环接收客户端数据

while ((count = recv(client_fd, read_buf, 1024, 0)))

{

if (count < 0)

{

syslog(LOG_ERR, "server recv error");

}

sprintf(log_buf, "服务端pid: %d: reveive message from client_fd: %d: %s", getpid(), client_fd, read_buf);

syslog(LOG_INFO, "%s", log_buf);

memset(log_buf, 0, 1024);

// 构造回复消息

sprintf(write_buf, "服务端pid: %d: reveived~\n", getpid());

send_count = send(client_fd, write_buf, 1024, 0);

}

// 客户端关闭连接

sprintf(log_buf, "服务端pid: %d: 客户端client_fd: %d请求关闭连接......\n", getpid(), client_fd);

syslog(LOG_NOTICE, "%s", log_buf);

sprintf(write_buf, "服务端pid: %d: receive your shutdown signal\n", getpid());

send_count = send(client_fd, write_buf, 1024, 0);

// 清理资源

sprintf(log_buf, "服务端pid: %d: 释放client_fd: %d资源\n", getpid(), client_fd);

syslog(LOG_NOTICE, "%s", log_buf);

shutdown(client_fd, SHUT_WR);

close(client_fd);

free(read_buf);

free(write_buf);

return;

}

int main(int argc, char const *argv\[\])

{

int temp_result;

struct sockaddr_in server_addr, client_addr; // 服务器和客户端地址结构

memset(&server_addr, 0, sizeof(server_addr));

memset(&client_addr, 0, sizeof(client_addr));

// 声明IPV4通信协议

server_addr.sin_family = AF_INET;

// 我们需要绑定0.0.0.0地址,转换成网络字节序后完成设置

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 端口随便用一个,但是不要用特权端口

server_addr.sin_port = htons(6666);

// 创建server socket

sockfd = socket(AF_INET, SOCK_STREAM, 0);

// 绑定地址

temp_result = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

// 进入监听模式

temp_result = listen(sockfd, 128);

socklen_t cliaddr_len = sizeof(client_addr);

// 注册信号处理函数,处理SIGCHLD信号,避免僵尸进程出现

signal(SIGCHLD, zombie_dealer);

// 处理SIGTERM函数,以优雅退出

signal(SIGTERM, sigterm_handler);

char log_buf1024;

memset(log_buf, 0, 1024);

// 接受client连接

while (1)

{

int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &cliaddr_len);

pid_t pid = fork(); // 为每个客户端创建子进程

if (pid > 0)// 父进程

{

sprintf(log_buf, "this is father, pid is %d, continue accepting...\n", getpid());

syslog(LOG_INFO, "%s", log_buf);

memset(log_buf, 0, 1024);

// 父进程不需要处理client_fd,释放文件描述符,使其引用计数减一,以便子进程释放client_fd后,其引用计数可以减为0,从而释放资源

close(client_fd);

}

else if (pid == 0)

{

// 子进程不需要处理sockfd,释放文件描述符,使其引用计数减一

close(sockfd);

sprintf(log_buf, "与客户端 from %s at PORT %d 文件描述符 %d 建立连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);

syslog(LOG_INFO, "%s", log_buf);

memset(log_buf, 0, 1024);

sprintf(log_buf, "新的服务端pid为: %d\n", getpid());

syslog(LOG_INFO, "%s", log_buf);

memset(log_buf, 0, 1024);

// 读取客户端数据,并打印到 stdout

read_from_client_then_write((void *)&client_fd);

// 释放资源并终止子进程

close(client_fd);

exit(EXIT_SUCCESS);

}

}

return 0;

}

---------------------------tcp_client.c------------------------

#include <sys/socket.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <pthread.h> // 使用线程

#define handle_error(cmd, result) \

if (result < 0) \

{ \

perror(cmd); \

return -1; \

}

void *read_from_server(void *argv)

{

int sockfd = *(int *)argv;

char *read_buf = NULL;

ssize_t count = 0;

read_buf = malloc(sizeof(char) * 1024);

if (!read_buf)

{

perror("malloc client read_buf");

return NULL;

}

// 循环接收服务端数据

while (count = recv(sockfd, read_buf, 1024, 0))

{

if (count < 0)

{

perror("recv");

}

fputs(read_buf, stdout); // 输出到标准输出

}

printf("收到服务端的终止信号......\n");

free(read_buf);

return NULL;

}

void *write_to_server(void *argv)

{

int sockfd = *(int *)argv;

char *write_buf = NULL;

ssize_t send_count;

write_buf = malloc(sizeof(char) * 1024);

if (!write_buf)

{

printf("写缓存申请异常,断开连接\n");

shutdown(sockfd, SHUT_WR); // 关闭写端

perror("malloc client write_buf");

return NULL;

}

// 从标准输入读取数据

while (fgets(write_buf, 1024, stdin) != NULL)

{

send(sockfd, write_buf, 1024, 0);

if (send_count < 0)

{

perror("send");

}

}

printf("接收到命令行的终止信号,不再写入,关闭连接......\n");

shutdown(sockfd, SHUT_WR);

free(write_buf);

return NULL;

}

int main(int argc, char const *argv\[\])

{

int sockfd, temp_result;

pthread_t pid_read, pid_write; // 两个线程:读和写

struct sockaddr_in server_addr;

memset(&server_addr, 0, sizeof(server_addr));

server_addr.sin_family = AF_INET;

// 连接本机 127.0.0.1

server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

// 连接端口 6666

server_addr.sin_port = htons(6666);

// 创建socket

sockfd = socket(AF_INET, SOCK_STREAM, 0);

handle_error("socket", sockfd);

// 连接server

temp_result = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

handle_error("connect", temp_result);

// 启动一个子线程,用来读取服务端数据,并打印到 stdout

pthread_create(&pid_read, NULL, read_from_server, (void *)&sockfd);

// 启动一个子线程,用来从命令行读取数据并发送到服务端

pthread_create(&pid_write, NULL, write_to_server, (void *)&sockfd);

// 主线程等待子线程退出

pthread_join(pid_read, NULL);

pthread_join(pid_write, NULL);

printf("关闭资源\n");

close(sockfd);

return 0;

}

当前Linux系统的系统日志文件路径为 /var/log/syslog,执行命令tail -F /var/log/syslog进行实时日志监控

  • tail = 显示文件末尾的内容
  • -F = 持续监控文件,即使文件被删除、重建、轮转(log rotation)也能继续跟踪
  • /var/log/syslog = Linux 系统的主要日志文件(记录系统、服务、守护进程等日志)
相关推荐
groundhappy1 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕2 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫3 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生3 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.115 小时前
linux基础IO
linux·运维·服务器
BelongPanda7 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯7 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998347 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器
yunwei377 小时前
eBPF 教程:检查 exec 后真正安装的可执行镜像
linux·开源
笑锝没心没肺8 小时前
yum update 报错:GPG key retrieval failed: [Errno 14] HTTPS Error 404 - Not Found
linux·运维·服务器