惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
IT之家
IT之家
C
Check Point Blog
T
The Blog of Author Tim Ferriss
S
SegmentFault 最新的问题
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
美团技术团队
M
MIT News - Artificial intelligence
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Security Blog
Microsoft Security Blog
G
Google Developers Blog
F
Fortinet All Blogs
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
Hugging Face - Blog
Hugging Face - Blog
MyScale Blog
MyScale Blog
爱范儿
爱范儿
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)

博客园 - 海乐学习

VS2026创建.net8的控制台程序并能在银河麒麟V10的Liunx上运行 在麒麟系统中卸载RabbitMQ Java Maven 开发的常用命令 C语言开发的常用命令 三汇Linux配置Config说明 Java实现优雅的关闭程序并执行清理流程的写法 将C语言开发的程序做成 麒麟系统的服务(Systemd 标准服务,Linux 通用) 实现开机自启动 ZeroMQ的DEALER双帧结构(路由空帧 + 业务帧)(支持异步收发、主动推送事件)C写服务端 java写客户端 ZeroMQ中ZMQ_DEALER单帧数据(支持异步收发、主动推送事件)C写服务端 java写客户端 win10系统中 关闭专注助手 执行 xx.sh 脚本文件时出现: /bin/bash^M:解释器错误: 没有那个文件或目录 运行编译打包好的 xxx.jar 中没有主清单属性 出现这个错误 将Java编译的 .jar文件做成 麒麟系统的服务(Systemd 标准服务,Linux 通用) 实现开机自启动 将Java编译的 .jar文件做成windows服务 实现开机自启动 方法二 RabbitMQ在麒麟系统中离线安装说明 VMware启动虚似机后出现 无法获取快照信息: 锁定文件失败 模块“Snapshot”启动失败。未能启动虚拟机。 C语言在Linux中开发完整Demo包含读配置文件写日志和定时器Timer C语言在Linux中开发读取配置文件app.conf 麒麟V10 Server系统中搭建C语言开发环境 麒麟ServerV10 修改IP4地址 麒麟ServerV10 配置IP4 当系统中有两个版本的Maven时,用IDEA创建Maven有时会出错 麒麟ServerV10安装 espeak-ng 和 ffmpeg 方法 C语言在Linux中开发没有界面纯后台运行的Demo程序(含日志和Timer) C语言在Linux中开发使用定时器Timer在界面上显示时间 C语言在Linux中开发带界面的程序(含每小时日志) C语言在Linux中开发第一个项目Hello Word 在apache-maven项目中使用log4写日志 在apache-maven项目中解决中文乱码问题 在apache-maven项目中读取配置文件
C语言开发中优雅的关闭程序并执行清理流程的写法
海乐学习 · 2026-06-17 · via 博客园 - 海乐学习

C语言开发中有下面这四种的退出程序的方法

1、Ctrl + C → 发送 SIGINT 信号

2、kill 进程PID(不带参数)→ 发送 SIGTERM   

3、kill -9 不执行任何清理

4、systemctl stop 停止服务 → 底层也是发送 SIGTERM

注:其中kill -9 是不执行任何清理的,所有在正常情况下不要使用

三种关闭操作对应的信号
1、Ctrl + C → 发送 SIGINT 信号
2、kill 进程PID(不带参数)→ 发送 SIGTERM
3、systemctl stop 停止服务 → 底层也是发送 SIGTERM
C 语言进程收到这两个信号,默认处理动作:立刻终止程序。

注意事项(生产环境必看)
信号处理函数内不能调用大量 IO / 复杂函数
ExitSys 内部需要主动关闭线程、zmq 上下文、释放硬件资源,否则会资源泄漏。
systemd 服务超时问题
如果 ExitSys 执行耗时较长(超过 systemd 默认 90 秒),服务会被强制发 SIGKILL(无法捕获,直接强杀)。
解决方案:
systemd 配置添加 TimeoutStopSec=120 延长停止等待时间;
ExitSys 内部所有关闭操作尽量缩短耗时。
kill -9 PID 无法捕获
任何代码都拦不住,进程瞬间销毁,不会执行任何清理,运维尽量不要用 kill -9。

实现原理

添加 SIGINT/SIGTERM 信号捕获 + 全局退出标记:
收到信号→标记置 1→while 循环结束→顺序执行 ExitSys、关闭日志,实现优雅退出程序

代码

主程序文件 ShPbxServer.c

#include <signal.h>          //退出信号
/*====================== 主函数 ======================*/
int main(void)
{
    // 重定向 stderr 以屏蔽 HMP 驱动的警告输出 (如 timer lost)
    //freopen("/dev/null", "w", stderr); //写这句退出信号就不能被补获到ExitSys()不能被执行

    sigset_t wait_sigset;
    siginfo_t sig_info;
    struct timespec zero_ts = {0, 0};
    int caught_sig;

    // 阻塞SIGINT/SIGTERM,子线程全部继承屏蔽,信号只留在主线程
    sigemptyset(&wait_sigset);
    sigaddset(&wait_sigset, SIGINT);
    sigaddset(&wait_sigset, SIGTERM);
    pthread_sigmask(SIG_BLOCK, &wait_sigset, NULL);

    // 仅屏蔽管道破裂信号,不处理退出信号
    signal(SIGPIPE, SIG_IGN);

    // 初始化日志
    Log_Init();
    Log_Write("=======================================");
    Log_Write("======== ShPbxServer v1.8 启动 ========");
    Log_Write("=======================================");

    if (InitSys() != 0)
    {
        Log_Write("[ShPbxServer][错误] 系统初始化失败");
        Log_Close();
        return -1;
    }
    Log_Write("[ShPbxServer][信息] 系统初始化成功,通道数:%d", nTotCh);
// ===================== 服务后台专用死循环(不退出、不阻塞、不占CPU) =====================
    //// 后台循环:只做非阻塞业务,不阻塞等待信号
    while (1)
    {// 后台死循环,微小sleep降低CPU,同时保证信号标记生效
        //sleep(1); // 每秒休眠一次,程序保持运行
        usleep(500000); // 500ms,比sleep(1)响应更快
        // 非阻塞检测是否收到退出信号
        // 正确传参:第二个参数 &sig_info 匹配 siginfo_t*
        caught_sig = sigtimedwait(&wait_sigset, &sig_info, &zero_ts);
        if (caught_sig > 0)
        {
            break;
        }
    }
    Log_Write("[ShPbxServer][信息] 收到停止信号,准备优雅退出...");
    ExitSys();
    Log_Close();// 关闭日志
    return 0;
}
/*====================== 系统退出 ======================*/
void ExitSys(void)
{
    //Log_Write("[ShPbxServer][信息] 正在关闭系统...");
// 1. 先关闭ZeroMQ(内部等待服务线程退出)
    Zmq_Cleanup();
    Log_Write("[ShPbxServer][信息] ZeroMQ 服务已关闭");

    // 2. 关闭音频推送ZeroMQ
    ZeroMQAudio_Cleanup();
    Log_Write("[ShPbxServer][信息] 音频流推送服务已关闭");

    // 3. 停止定时器线程
    if (bSetTime == 1)
    {
        KillTimer(Th);
        Log_Write("[ShPbxServer][信息] Timer定时器已停止");
    }

    // 4. 关闭CTI硬件,停止所有异步回调线程
    Api_SsmCloseCti();
    Log_Write("[ShPbxServer][信息] CTI 系统已关闭板卡SsmCloseCti()");
    usleep(200000);  // 延长等待,让回调函数全部执行完毕退出

    // 最后销毁互斥锁
    pthread_mutex_destroy(&g_TrkChMutex);
 
}