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 buf[1024];

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 buf[1024];

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_buf[1024];

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_buf[1024];

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 系统的主要日志文件(记录系统、服务、守护进程等日志)
相关推荐
Nerd Nirvana2 小时前
IPv6组播在DLMS协议中的应用——基础知识掌握
linux·运维·服务器·网络·网络协议·ipv6·dlms协议
福尔摩斯张2 小时前
TCP/IP网络编程深度解析:从Socket基础到高性能服务器构建(超详细)
linux·运维·服务器·开发语言·网络·网络协议·tcp/ip
Sleepy MargulisItG2 小时前
【Linux网络编程】传输层协议:TCP
linux·网络·tcp/ip
卡布叻_星星2 小时前
Docker之Windows与Linux不同架构部署理解
linux·windows·docker
Cat God 0072 小时前
基于 CentOS 7.6 的 MySQL 8.0 主从复制
linux·服务器·mysql·centos
春日见2 小时前
如何跑通,吃透一个开源项目?
linux·运维·开发语言·数码相机·matlab
用户6135411460163 小时前
【麒麟Kylin】cmake-3.16.5 rpm包安装步骤详解 附常见问题
linux
AAA_bo13 小时前
liunx安装canda、python、nodejs、git,随后部署私有网页内容提取工具--JinaReader全攻略
linux·python·ubuntu·typescript·aigc·python3.11·jina
代码游侠3 小时前
学习笔记——Linux进程间通信(IPC)
linux·运维·笔记·学习·算法