forked from gary/ems
2
0
Fork 0
sun_ems/ems_c/daemon.c

290 lines
9.7 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*****************************************************************************
* @copyright 2024-2024, . POWER SUPPLY CO., LTD.
* @file daemon.c
* @brief 进程守护
* @author Gary
* @date 2024/09/20
* @remark
*****************************************************************************/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "kernel/kit_log.h"
#include "daemon.h"
#define STOP_TIMEOUT 10000
#define STOP_TICK 2000
/*****************************************************************************
* @brief 给定文件描述符fd尝试对整个文件加写锁F_WRLCK以确保该文件在当前进程中被锁定
* @param[in] fd文件描述符
* @return 1、小于0表示锁失败了1当失败的原因是EACCES和EAGAIN表示已经被锁定了2其他错误表示系统异常了
* 2、大于等于0时表示锁成功了
*****************************************************************************/
static int try_lock_file(int fd)
{
// flock 结构用于描述文件锁的属性
struct flock fl;
// 设置 fl 的 l_type 字段为 F_WRLCK表示我们要申请一个写锁exclusive lock。写锁意味着其他进程不能对该文件进行写操作。
fl.l_type = F_WRLCK;
// 设置 fl 的 l_type 字段为 F_WRLCK表示我们要申请一个写锁exclusive lock。写锁意味着其他进程不能对该文件进行写操作。
fl.l_start = 0;
// 设置 fl 的 l_whence 字段为 SEEK_SET表示 l_start 是相对于文件开头的偏移量。
fl.l_whence = SEEK_SET;
// 设置 fl 的 l_len 字段为 0表示锁定的长度为0这意味着锁定整个文件直到文件结束
fl.l_len = 0;
return fcntl(fd, F_SETLK, &fl);
}
/*****************************************************************************
* @brief 等待守护进程退出
* @param[in] ms表示等待的最大时间毫秒
* @return 0-成功1-失败
*****************************************************************************/
static int wait_exit(uint32_t ms)
{
uint32_t tick = 0; // 记录当前已经等待时间(毫秒)
int ret = 1;
int fd = -1;
// 以只写模式打开文件
fd = open(DAEMON_LOCK_FNAME, O_WRONLY);
if (fd < 0)
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法打开文件%s错误原因%s", DAEMON_LOCK_FNAME, strerror(errno));
exit(1);
}
while (tick < ms)
{
// 尝试锁PID文件
if (try_lock_file(fd) < 0)
{
// 锁定失败时错误是EACCES和EAGAIN时说明进程正在运行
if (EACCES == errno || EAGAIN == errno)
{
KITPTF(LOG_DAEMON_EN, INFO_EN, "_EMS_C_V1.0.0 停止中");
}
// 锁定失败时,非正常锁定异常
else
{
close(fd);
KITLOG(LOG_DAEMON_EN, ERROR_EN, "锁定'%s'发生错误,错误原因:%s", DAEMON_LOCK_FNAME, strerror(errno));
exit(1);
}
}
else
{
KITPTF(LOG_DAEMON_EN, INFO_EN, "_EMS_C_V1.0.0 已停止");
ret = 0;
break;
}
// 休眠STOP_TICK毫秒
struct timespec tv = {
.tv_sec = STOP_TICK / 1000,
.tv_nsec = (STOP_TICK % 1000) * 1000000,
};
nanosleep(&tv, NULL);
tick += STOP_TICK;
}
close(fd);
return ret;
}
/*****************************************************************************
* @brief 守护进程启动函数
* @return
*****************************************************************************/
void daemonize()
{
struct rlimit rl; // 存储资源限制信息
int descriptors; // 用来记录最大文件描述符
pid_t pid; // 进程 ID用于创建子进程
struct sigaction sa; // 信号结构体
int ret = 1;
// 调用 umask(0) 清除文件创建权限掩码,确保新创建文件拥有预期的权限
umask(0);
// 调用 getrlimit() 获取进程可打开的最大文件描述符数。如果获取失败,退出程序
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
{
KITPTF(LOG_DAEMON_EN, ERROR_EN, "获取进程可打开的最大文件描述符数失败,失败原因:%s", strerror(errno));
exit(1);
}
// 将文件描述符限制保存在 nfile 中。如果文件描述符限制为 RLIM_INFINITY则设置为 1024否则使用 rl.rlim_max
descriptors = (RLIM_INFINITY == rl.rlim_max) ? 1024 : rl.rlim_max;
// 调用 fork() 创建子进程。父进程退出,子进程继续执行。
// 调用 setsid() 让子进程成为新会话的领导者,并且失去控制终端。
if ((pid = fork()) < 0)
{
exit(1);
}
else if (0 != pid)
{
exit(0);
}
setsid();
// 忽略 SIGHUP 信号,防止关闭终端时进程也被终止。
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
exit(1);
}
// 再次调用 fork(),以确保新进程不能再次获得控制终端。父进程退出,子进程继续执行。
if ((pid = fork()) < 0)
{
exit(1);
}
else if (0 != pid)
{
exit(0);
}
// 关闭当前进程打开的所有文件描述符,以便守护进程完全独立。
for (int i = 0; i < descriptors; ++i)
{
close(i);
}
// 将把文件描述符 0标准输入、1标准输出和 2标准错误附加到 /dev/null确保守护进程不依赖终端
open("/dev/null", O_RDWR);
ret = dup(0);
assert(ret != -1);
ret = dup(0);
assert(ret != -1);
}
/*****************************************************************************
* @brief 检查守护进程是否已运行
* @return 0-没有运行进行上锁1-正在运行
*****************************************************************************/
int checkDaemonRunning()
{
int ret = 1;
int fd = -1;
ssize_t size = -1;
char buf[16] = {0};
// 调用 umask(0) 清除文件创建权限掩码,确保新创建文件拥有预期的权限
umask(0);
// 以读写模式,并创建模式的打开文件
fd = open(DAEMON_LOCK_FNAME, O_RDWR | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd < 0)
{
// 再次以读写模式,打开文件
fd = open(DAEMON_LOCK_FNAME, O_RDWR,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd < 0)
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法打开文件%s错误原因%s", DAEMON_LOCK_FNAME, strerror(errno));
exit(1);
}
}
// 尝试锁定文件
if (try_lock_file(fd) < 0)
{
// 锁定失败时错误是EACCES和EAGAIN时说明进程正在运行
if (EACCES == errno || EAGAIN == errno)
{
close(fd);
return 1;
}
KITLOG(LOG_DAEMON_EN, ERROR_EN, "锁定'%s'发生错误,错误原因:%s", DAEMON_LOCK_FNAME, strerror(errno));
exit(1);
}
// 清空文件的内容,使其变为空文件
ret = ftruncate(fd, 0);
if (ret < 0)
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法清空文件%s错误原因%s", DAEMON_LOCK_FNAME, strerror(errno));
exit(1);
}
// 获取文件锁时向文件里写入PID进程号
snprintf(buf, sizeof(buf), "%ld", (long)getpid());
size = write(fd, buf, strlen(buf) + 1);
if (size < 0)
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法写入文件%s错误原因%s", DAEMON_LOCK_FNAME, strerror(errno));
exit(1);
}
return 0;
}
/*****************************************************************************
* @brief 停止守护进程
* @return 0-成功1-失败2-无法打开文件; 3-无法读取文件; 4-无法根据PID值获取进程的pid_t结构体;
*****************************************************************************/
int stop_daemon()
{
int ret = 1;
FILE *fp = fopen(DAEMON_LOCK_FNAME, "r");
if (NULL == fp)
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法打开文件%s错误原因%s", DAEMON_LOCK_FNAME, strerror(errno));
return 2;
}
long pid = -1;
if (1 != fscanf(fp, "%ld", &pid))
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法读取文件%s的PID值。", DAEMON_LOCK_FNAME);
fclose(fp);
return 3;
}
pid_t gid = getpgid((pid_t)pid);
if (-1 == gid)
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法根据PID值获取进程的pid_t结构体错误原因%s。", strerror(errno));
fclose(fp);
return 4;
}
// 向进程组发送 SIGINT 信号,等待进程优雅退出。如果超时,发送 SIGKILL 强制杀死进程。
if (0 == kill((pid_t)(-gid), SIGINT))
{
if (wait_exit(STOP_TIMEOUT) == 0)
{
ret = 0;
}
else
{
if (0 == kill((pid_t)(-gid), SIGKILL))
{
ret = 0;
}
}
}
else
{
KITLOG(LOG_DAEMON_EN, ERROR_EN, "无法杀死gpid值%d错误原因%s。", (long)gid, strerror(errno));
}
return ret;
}
#endif