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

290 lines
9.7 KiB
C
Raw Permalink Normal View History

2025-05-13 17:49:49 +08:00
/*****************************************************************************
* @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 fdF_WRLCK
* @param[in] fd
* @return 101EACCES和EAGAIN2
* 20
*****************************************************************************/
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