外贸多语种网站推广惠州专门做网站
- 作者: 五速梦信息网
- 时间: 2026年04月20日 08:21
当前位置: 首页 > news >正文
外贸多语种网站推广,惠州专门做网站,网站框架搭建设计,常德公司网站建设文章目录 一、Linux进程信号核心概念1.1 信号本质1.2 关键术语1.3 Linux 信号机制的核心流程#xff1a; 二、信号产生机制全景2.1 通过终端按键产生信号2.1.1 基本操作 2.2 调用系统命令向进程发信号2.2.1 kill 命令#xff1a;向指定进程发送信号2.2.2 killall 命令#x… 文章目录 一、Linux进程信号核心概念1.1 信号本质1.2 关键术语1.3 Linux 信号机制的核心流程 二、信号产生机制全景2.1 通过终端按键产生信号2.1.1 基本操作 2.2 调用系统命令向进程发信号2.2.1 kill 命令向指定进程发送信号2.2.2 killall 命令按进程名发送信号2.2.3 pkill 命令按进程名或属性发送信号2.2.4 发送信号的实际场景2.2.5 查看信号列表 2.3 使用函数产生信号2.3.1 kill2.3.2 raise() 函数向自身发送信号2.3.3 sigqueue() 函数发送带数据的信号实时信号2.3.4 信号发送的错误处理与注意事项2.3.5 总结 2.4 由软件条件产生信号2.4.1 alarm2.4.2 如何简单快速理解系统闹钟 2.5 硬件异常产生信号2.5.1 常见的硬件异常信号2.5.2 硬件异常的处理流程2.5.3 调试硬件异常信号 三、保存信号3.1 信号其他相关常见概念3.2 在内核中的表示3.3 sigset_t信号集3.4 信号集操作函数3.4.1 sigprocmask总结 3.4.2 sigpending 四、捕获信号4.1 信号捕捉的流程4.2 sigaction4.3 操作系统是怎么运行的4.3.1 硬件中断4.3.2 时钟中断4.3.3 死循环4.3.4 软中断4.3.5 缺页中断内存碎片处理除零野指针错误? 4.4 如何理解内核态和用户态 五、可重入函数5.1 什么是可重入函数5.2 不可重入的典型场景与风险5.3 可重入函数的设计原则5.4 可重入函数的实现示例5.5 五、信号处理中的可重入性 六、volatile6.1 volatile 的本质与作用6.2 信号处理函数中的全局变量 七、SIGCHLD 信号Linux 进程管理的 “子进程通知机制”7.1 SIGCHLD 信号的本质与作用7.2 SIGCHLD 的默认行为与问题7.3 处理 SIGCHLD 的三种方式7.4 调试与监控7.5 常见误区与注意事项7.6 总结 一、Linux进程信号核心概念 1.1 信号本质
异步通信机制事件驱动的进程间通知
信号类型预定义整数1-31为常规信号34为实时信号
生命周期产生 → 保存 → 处理1.2 关键术语 术语描述内核表示递达(Delivery)信号实际处理过程task_struct-ksigaction未决(Pending)信号产生到递达间的状态task_struct-signal-pending阻塞(Block)进程主动屏蔽的信号task_struct-blocked 位图 1.3 Linux 信号机制的核心流程 信号产生 — 信号保存 — 信号处理 二、信号产生机制全景 2.1 通过终端按键产生信号 2.1.1 基本操作 Ctrl C(SIGINT) 向当前正在运行的前台进程发送中断信号使进程立即终止运行。不过若进程对SIGINT信号进行了特殊处理如捕获并忽略该信号那么按下Ctrl C可能无法终止进程。同时Ctrl C仅对前台进程有效后台进程不会受其影响Ctrl (SIGOUT) 不仅会终止进程还会让进程生成 核心转储文件core dump用于调试程序崩溃问题Ctrl Z(SIGSTP) 将当前前台进程暂停挂起 并放入后台使其暂时停止运行但不终止。 2.2 调用系统命令向进程发信号 2.2.1 kill 命令向指定进程发送信号 基本语法 kill [-信号名称/编号] 进程ID常用信号选项 -9 或 -SIGKILL强制终止进程不可被捕获或忽略。 -15 或 -SIGTERM正常终止进程默认选项可被捕获并执行清理。 -1 或 -SIGHUP重新加载配置常用于守护进程如 nginx。 -2 或 -SIGINT中断进程等价于 Ctrl C。 -3 或 -SIGQUIT终止进程并生成 core 文件等价于 Ctrl \。 -19 或 -SIGSTOP暂停进程等价于 Ctrl Z不可被忽略。 -18 或 -SIGCONT恢复被暂停的进程。 示例
正常终止进程先尝试清理
kill 1234# 强制终止进程不执行清理 kill -9 1234# 向多个进程发送信号 kill -15 1234 5678 9012# 发送自定义信号如 SIGUSR1编号 10 kill -10 12342.2.2 killall 命令按进程名发送信号 基本语法 killall [-信号名称/编号] 进程名示例
终止所有名为 nginx 的进程
killall nginx# 强制终止所有名为 cpp 的进程 killall -9 cpp# 重新加载所有名为 httpd 的进程的配置 killall -HUP httpd2.2.3 pkill 命令按进程名或属性发送信号 基本语法 pkill [-信号名称/编号] [-选项] 匹配模式常用选项 -u 用户按用户名筛选进程。-t 终端按终端会话筛选进程。-f匹配进程全名包括命令行参数。 示例
终止用户 test 运行的所有 bash 进程
pkill -u test bash# 暂停当前终端的所有 vim 进程 pkill -STOP -t pts/0 vim# 终止包含 python script.py 的进程 pkill -f python script.py2.2.4 发送信号的实际场景 优雅重启服务
重新加载 Nginx 配置不中断现有连接
kill -HUP $(cat /run/nginx.pid)批量管理进程
暂停所有用户 alice 的进程
pkill -STOP -u alice# 恢复所有被暂停的进程 pkill -CONT -u alice终止顽固进程
先尝试正常终止给进程清理资源的机会
kill 1234# 若 5 秒后仍未终止强制杀死 sleep 5 kill -9 12342.2.5 查看信号列表 通过 kill -l 命令可查看系统支持的所有信号 编号34以上的是实时信号本章只讨论编号34以下的信号不讨论实时信号。这些信号各自在什么条件下产生默认的处理动作是什么在signal(7)中都有详细说明man 7 signal 2.3 使用函数产生信号 2.3.1 kill 函数原型 #include sys/types.h #include signal.h int kill(pid_t pid, int sig);参数说明 pid目标进程 IDpid 0或特殊值 pid 0向当前进程组的所有进程发送信号。pid -1向所有有权限发送的进程发送信号。sig要发送的信号如 SIGINT、SIGKILL或自定义信号如 SIGUSR1。
返回值 成功返回 0失败返回 -1错误原因可通过 errno 获取。 代码示例向指定进程发送 SIGTERM 信号 #include stdio.h #include stdlib.h #include sys/types.h #include signal.h #include errno.h #include string.hint main(int argc, char *argv[]) {if (argc ! 3) {printf(用法: %s 进程ID 信号编号\n, argv[0]);return 1;}pid_t target_pid atoi(argv[1]);int signal_num atoi(argv[2]);if (kill(target_pid, signal_num) -1) {perror(kill 失败);printf(错误码: %d, 错误信息: %s\n, errno, strerror(errno));return 1;}printf(已向进程 %d 发送信号 %d\n, target_pid, signal_num);return 0; }编译与使用 gcc -o kill_demo kill_demo.c向进程1234发送SIGTERM信号15
./kill_demo 1234 152.3.2 raise() 函数向自身发送信号 函数原型 #include signal.h int raise(int sig);参数说明 sig要发送的信号等价于 kill(getpid(), sig)。 返回值 成功返回 0失败返回非零值。 代码示例程序自中断等价于 Ctrl C #include stdio.h #include signal.h #include unistd.hvoid sigint_handler(int sig) {printf(捕获到SIGINT信号程序即将退出\n);exit(0); // 调用 exit 终止进程 }int main() {// 注册SIGINT信号处理函数signal(SIGINT, sigint_handler);printf(程序运行中3秒后自发送SIGINT信号…\n);sleep(3);// 向自身发送SIGINT信号raise(SIGINT);printf(该语句不会执行因为进程已处理信号并退出\n);return 0; }2.3.3 sigqueue() 函数发送带数据的信号实时信号 函数原型 #include sys/types.h #include signal.h int sigqueue(pid_t pid, int sig, const union sigval value);参数说明 pid目标进程 ID。sig要发送的信号推荐使用实时信号如 SIGRTMIN n。value包含整数或指针数据的联合体可随信号传递给目标进程。 返回值 成功返回 0失败返回 -1。 代码示例发送带数据的实时信号 #include stdio.h #include stdlib.h #include sys/types.h #include signal.h #include unistd.h #include string.h// 目标进程接收信号 void target_process() {// 注册信号处理函数struct sigaction sa;memset(sa, 0, sizeof(sa));sa.sa_flags SA_SIGINFO; // 支持接收信号附带的数据sa.sa_sigaction {if (sig SIGRTMIN) {printf(接收实时信号SIGRTMIN附带数据%d\n, info-si_int);}};sigaction(SIGRTMIN, sa, NULL);printf(目标进程运行中等待信号…\n);while (1) sleep(1); }// 发送信号的进程 void sender_process(pid_t target_pid) {union sigval value;value.sival_int 100; // 附带整数数据if (sigqueue(target_pid, SIGRTMIN, value) -1) {perror(sigqueue 失败);exit(1);}printf(已向进程 %d 发送带数据的SIGRTMIN信号\n, target_pid); }int main(int argc, char *argv[]) {if (argc ! 2) {printf(用法: %s 0(目标)/1(发送者)\n, argv[0]);return 1;}int mode atoi(argv[1]);if (mode 0) {target_process();} else if (mode 1) {pid_t target_pid 1234; // 替换为实际目标进程IDsender_process(target_pid);} else {printf(模式错误需输入0或1\n);}return 0; }2.3.4 信号发送的错误处理与注意事项 常见错误 权限不足普通用户只能向自己的进程发送信号向其他用户进程发送信号需 root 权限。进程不存在目标进程已终止或 PID 错误时kill 会返回 ESRCH 错误。信号被阻塞目标进程若阻塞了该信号信号会暂存直至阻塞解除。 推荐方式 先检查进程是否存在使用 kill(pid, 0) 可在不发送信号的情况下检查进程是否存在sig0 为 “空信号”。区分信号类型 非实时信号如 SIGINT若多次发送且未处理仅保留最后一次。实时信号如 SIGRTMIN会排队等待处理适合需要可靠传递的场景。 避免滥用 SIGKILL优先使用 SIGTERM 让进程优雅退出仅在必要时使用 SIGKILL。 2.3.5 总结 函数用途核心参数适用场景kill() 向任意进程发送信号pid进程 ID、sig信号进程控制、常规信号发送raise()向自身发送信号sig信号程序自中断、自定义退出sigqueue()发送带数据的实时信号pid、sig、value数据进程间通信、需传递数据场景 2.4 由软件条件产生信号 常见的软件信号 SIGALRM闹钟信号 触发条件通过alarm()或setitimer()函数设置的定时器到期。应用场景实现超时控制、周期性任务如心跳检测。 SIGUSR1/SIGUSR2用户自定义信号 触发条件通过kill()、raise()或sigqueue()函数手动发送。应用场景进程间自定义通信如通知配置更新、优雅重启。 SIGPIPE管道破裂信号 触发条件向已关闭的管道或套接字写入数据。应用场景网络编程中检测连接状态。 SIGALRM/SIGVTALRM虚拟定时器信号 触发条件通过setitimer()设置的用户态或内核态 CPU 时间到期。 应用场景性能分析、CPU 时间统计。 SIGPIPE是一种由软件条件产生的信号在“管道”中已经介绍过了。本节主要介绍alarm函数和SIGALRM信号。 2.4.1 alarm 基本功能与原型 函数原型 #include unistd.h unsigned int alarm(unsigned int seconds);参数说明 seconds设置的定时器秒数。seconds 0取消之前设置的闹钟。seconds 0在 seconds 秒后触发 SIGALRM 信号。 返回值 返回之前设置的闹钟剩余秒数若之前未设置则返回 0。 默认行为与信号处理 默认行为 当定时器到期时进程会收到 SIGALRM 信号默认行为是终止进程。自定义处理 可通过 signal() 或 sigaction() 注册信号处理函数避免进程被终止。 应用场景 超时控制 例如等待用户输入或网络请求时设置超时。周期性任务 结合信号处理实现简单的定时器。资源监控 定时检查系统资源使用情况。 代码示例 示例 1基本超时控制 #include stdio.h #include unistd.h #include signal.h #include stdlib.hvoid timeout_handler(int sig) {printf(超时程序已运行超过5秒\n);exit(1); }int main() {// 注册SIGALRM信号处理函数signal(SIGALRM, timeout_handler);// 设置5秒后触发SIGALRM信号alarm(5);printf(程序开始运行等待5秒…\n);sleep(10); // 尝试休眠10秒但会在5秒后被中断printf(该语句不会执行因为进程已被信号中断\n);return 0; }示例 2非阻塞超时读取用户输入 #include stdio.h #include unistd.h #include signal.h #include string.hvolatile int input_received 0;void alarm_handler(int sig) {printf(\n超时请加快输入\n);input_received 1; }int main() {char buffer[100];// 注册信号处理函数signal(SIGALRM, alarm_handler);// 设置3秒超时alarm(3);printf(请在3秒内输入内容);fgets(buffer, sizeof(buffer), stdin);// 取消闹钟如果用户在超时前输入alarm(0);if (!input_received) {printf(你输入了%s, buffer);}return 0; }示例 3实现周期性任务 #include stdio.h #include unistd.h #include signal.hvoid periodic_task(int sig) {printf(执行周期性任务每秒打印一次\n);alarm(1); // 重新设置1秒后触发 }int main() {// 注册信号处理函数signal(SIGALRM, periodic_task);// 启动第一个闹钟alarm(1);printf(程序运行中按CtrlC终止…\n);while (1) {// 主循环保持程序运行pause(); // 等待信号}return 0; }注意事项 每个进程只能有一个闹钟多次调用 alarm() 会覆盖之前的设置。时间精度有限alarm() 基于秒级计时不适合毫秒级精度场景。信号处理函数应简洁避免在信号处理函数中执行复杂操作可能导致重入问题。与 sleep() 冲突alarm() 会中断 sleep()、pause() 等系统调用。
2.4.2 如何简单快速理解系统闹钟 系统闹钟其实本质是OS必须自身具有定时功能并能让用户设置这种定时功能才可能实现闹钟这样的技术。 现代Linux是提供了定时功能的定时器也要被管理先描述在组织。内核中的定时器数据结构是 #include linux/timer.hstruct timer_list {struct list_head entry; // 内核链表结构unsigned long expires; // 到期时间jiffiesstruct tvec_base *base; // 内部使用的定时器基数void (*function)(unsigned long); // 回调函数unsigned long data; // 传递给回调函数的参数int slack; // 定时器执行的松弛时间// …其他字段内核版本不同可能有差异 };操作系统管理定时器采用的是时间轮的做法。 核心原理将时间划分为固定槽位每个槽存储到期时间相同的定时器。指针随时间移动到期时触发对应槽的定时器。 这里就简单提一下感兴趣的自己去了解一下。如果难以理解你就将它理解为一个时间轴谁的过期时间更近那么就先调度谁。 2.5 硬件异常产生信号 2.5.1 常见的硬件异常信号 SIGSEGV段错误信号 11 触发原因 进程访问未分配给它的内存如空指针解引用、数组越界。硬件机制 MMU内存管理单元检测到非法地址触发页错误Page Fault。示例场景 int *ptr NULL; ptr 10; // 触发SIGSEGVSIGFPE浮点异常信号 8 触发原因 数学运算错误如除零、溢出。硬件机制 CPU 的浮点运算单元FPU检测到错误。示例场景 int a 1 / 0; // 触发SIGFPE整数除零 double b 1.0 / 0.0; // 可能触发取决于编译器和硬件SIGILL非法指令信号 4 触发原因 CPU 执行了无效指令如未实现的指令、错误的操作码。硬件机制 指令解码器检测到非法指令。示例场景 // 手动构造非法指令示例仅示意实际不可执行 unsigned char code[] {0xFF, 0xFF, 0xFF}; // 无效操作码 ((void ()())code)(); // 触发SIGILLSIGBUS总线错误信号 7 触发原因 硬件访问错误如未对齐内存访问、物理内存损坏。硬件机制 内存总线检测到错误。示例场景 // 在某些架构上访问未对齐的内存可能触发SIGBUS struct {int a;char b; } attribute((packed)) s; int p (int)s.b; // 未对齐的指针 *p 10; // 可能触发SIGBUS2.5.2 硬件异常的处理流程 异常发生CPU 执行指令时检测到错误如除零、无效内存访问。硬件中断CPU 切换到内核模式执行对应的中断处理程序。信号生成内核识别异常类型构造对应的信号如SIGSEGV。信号传递内核将信号添加到目标进程的未决信号队列。进程响应 默认行为终止进程生成核心转储文件core dump。自定义处理若进程通过signal()或sigaction()注册了处理函数则执行该函数。
2.5.3 调试硬件异常信号 子进程退出 core dump
核心转储文件core dump 作用保存进程崩溃时的内存状态用于事后分析。启用方法 ulimit -c unlimited # 允许生成core文件分析工具 gdb ./program core # 用GDB加载程序和core文件GDB 调试技巧设置信号处理方式捕获但不终止
(gdb) handle SIGSEGV nostop print# 运行程序直到崩溃 (gdb) run# 查看堆栈跟踪 (gdb) backtrace# 查看变量值 (gdb) print variable三、保存信号 3.1 信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery)信号从产生到递达之间的状态称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态直到进程解除对此信号的阻塞才执行递达的动作。注意阻塞和忽略是不同的只要信号被阻塞就不会递达而忽略是在递达之后可选的一种处理动作。 3.2 在内核中的表示 示意图 task_struct 中的信号字段 每个进程的描述符task_struct包含以下信号相关字段 struct task_struct {// 信号掩码当前阻塞的信号sigset_t blocked;// 未决信号pendingstruct signal_struct *signal;// 信号处理函数表struct k_sigaction ksigaction[_NSIG];// 其他字段… };signal_struct 结构 struct signal_struct {atomic_t count; // 引用计数struct sigpending pending; // 未决信号队列spinlock_t siglock; // 保护锁struct sigaction action[_NSIG]; // 用户空间信号处理函数// 其他字段… };sigpending 结构 struct sigpending {struct list_head list; // 未决信号链表sigset_t signal; // 未决信号位图 };信号处理流程 信号产生内核 / 其他进程通过系统调用如 kill() 发送信号标记 pending 对应位为 1。检查阻塞进程调度或从内核态返回用户态时检查 block若信号被阻塞block1 则跳过处理维持 pending1若未阻塞block0 进入下一步。执行处理动作根据 handler 配置执行默认动作SIG_DFL 、忽略SIG_IGN 或自定义函数sighandler 处理后清零 pending 对应位。 3.3 sigset_t信号集 sigset_t 本质上是一个 位图Bitmap每个位对应一个信号编号 位数通常为 64 位对应 64 个信号。实现内核中定义为 unsigned long 数组 typedef struct {unsigned long sig[_NSIG_WORDS]; // _NSIG_WORDS 通常为 264位系统 } sigset_t;信号表示 若信号集中包含信号 sig则对应位被置为 1。例如信号集包含 SIGINT2则第 2 位为 1。 3.4 信号集操作函数 初始化与修改 #include signal.h// 清空信号集所有位设为 0 int sigemptyset(sigset_t *set);// 填充信号集所有位设为 1 int sigfillset(sigset_t *set);// 添加信号到集合 int sigaddset(sigset_t *set, int signum);// 从集合中移除信号 int sigdelset(sigset_t *set, int signum);// 检查信号是否在集合中 int sigismember(const sigset_t *set, int signum);信号掩码操作 // 设置当前进程的信号掩码阻塞/解除阻塞信号 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);how 参数 SIG_BLOCK添加 set 中的信号到当前掩码阻塞这些信号。SIG_UNBLOCK从当前掩码中移除 set 中的信号解除阻塞。SIG_SETMASK用 set 替换当前掩码。
3.4.1 sigprocmask 函数原型 #include signal.hint sigprocmask(int how, const sigset_t *set, sigset_t *oldset);参数说明 how操作类型可选值 SIG_BLOCK将 set 中的信号添加到当前掩码阻塞这些信号。SIG_UNBLOCK从当前掩码中移除 set 中的信号解除阻塞。SIG_SETMASK用 set 完全替换当前掩码。 set信号集指针指定要操作的信号。若为 NULL则不修改掩码仅获取当前掩码到 oldset。oldset用于保存修改前的信号掩码若不为 NULL可用于后续恢复。 返回值 成功返回 0。失败返回 -1并设置 errno如 EFAULT、EINVAL。 信号掩码与未决信号 信号掩码Signal Mask 本质是一个位图每位对应一个信号如 SIGINT、SIGTERM。被掩码标记的信号不会被进程接收而是进入 未决状态Pending。 未决信号Pending Signals 已产生但被阻塞的信号会暂存到进程的未决队列。掩码解除后未决信号会被立即处理非实时信号可能合并实时信号支持排队。 总结 pending信号集记录已生成但未处理的信号阻塞信号集决定哪些信号会被延迟处理。当信号产生时 若阻塞信号集对应位为0未阻塞无论pending集状态如何信号都会被立即递送若阻塞信号集对应位为1阻塞信号会被加入pending集pending位设为1保持未决状态 当进程通过sigprocmask()解除信号阻塞将阻塞位设为0后 内核检查pending集若对应信号位为1存在未决信号在下次进程从内核态返回用户态的执行上下文中该信号会被递送处理 关键点说明 递送时机信号处理发生在进程从内核态返回用户态时这是Linux信号设计的核心机制 系统调用返回时硬件中断处理完成后进程上下文切换时 特殊情形 连续多次阻塞同一信号只有第一次会进入pending标准信号SIGKILL和SIGSTOP不能被阻塞实时信号RT信号会排队不丢失FIFO
3.4.2 sigpending 函数原型 #include signal.hint sigpending(sigset_t *set);参数 set指向 sigset_t 类型的指针用于存储当前未决信号集合。 返回值 成功返回 0并将未决信号集复制到 set 中。失败返回 -1并设置 errno通常为 EFAULT表示 set 指针无效。 四、捕获信号 4.1 信号捕捉的流程 信号捕捉时进程执行主控制流遇中断、异常或系统调用进入内核态内核处理完相关事务准备回用户态前经 do_signal() 检查当前进程可递送信号若为自定义处理函数的信号内核保存进程主控制流上下文让 CPU 跳转到用户态执行信号处理函数处理函数返回时借 sigreturn 再次陷入内核内核通过 sys_sigreturn 恢复进程之前保存的主控制流上下文最终进程回到用户态从主控制流上次被中断处继续执行 实现异步信号的 “中断 - 处理 - 恢复” 流程保障主逻辑被打断后可无缝续行。 这条水平线就是用户层代码和内核底层逻辑的 “分界线”程序在不同权限、功能区域执行时会以此为界完成切换是理解信号处理中用户态与内核态交互的基础标识。 4.2 sigaction 函数原型 #include signal.h int sigaction(int signum, const struct sigaction *act, struct sigaction oldact);参数 signum要操作的信号编号如 SIGINT、SIGTERM、SIGCHLD 等 SIGKILL、SIGSTOP 这类内核强制处理的信号无法通过它修改行为 。act指向 struct sigaction 结构体的指针**设置新的信号处理行为 **。若为 NULL则不修改信号行为仅用于查询。oldact指向 struct sigaction 结构体的指针用于保存信号原来的处理行为 。若为 NULL则不保存。 返回值 成功返回 0失败返回 -1 并设置 errno如信号编号无效、指针参数非法等。 功能 允许进程设置、查询特定信号的处理逻辑定义进程收到信号时应执行的操作比如 捕获信号并执行自定义处理函数如程序崩溃时记录日志。恢复信号的默认行为如让 SIGINT 恢复 “终止进程” 的默认动作 。忽略特定信号如忽略 SIGCHLD 避免子进程变成僵尸进程的场景优化 。 4.3 操作系统是怎么运行的 4.3.1 硬件中断 中断向量表就是操作系统的一部分启动就加载到内存中了通过外部硬件中断操作系统就不需要对外设进行任何周期性的检测或者轮询由外部设备触发的中断系统运行流程叫做硬件中断 4.3.2 时钟中断 问题: 进程可以在操作系统的指挥下被调度被执行那么操作系统自己被谁指挥被谁推动执行呢外部设备可以触发硬件中断但是这个是需要用户或者设备自己触发有没有自己可以定期触发的设备
答 操作系统的执行依赖于硬件引导流程与中断驱动机制本质是 “被动响应事件” 的系统而非被外部实体 “指挥”。定时器设备通过周期性中断为操作系统提供时间基准是实现进程调度、时间管理的核心硬件基础其作用如同系统的 “心跳”。理解这两点有助于深入掌握计算机系统的底层运行逻辑。 这样操作系统就能在硬件的推动下自动调度了。 4.3.3 死循环 如果是这样操作系统不就可以躺平了吗对操作系统自己不做任何事情需要什么功能就向中断向量表里面添加方法即可。操作系统的本质就是一个死循环 这样操作系统就可以在硬件时钟的推动下自动调度了。所以什么是时间片CPU为什么会有主频为什么主频越快CPU越快 答 时间片是多任务系统分配 CPU 时间的基本单位决定了进程切换的粒度。主频是 CPU 每秒的时钟周期数直接影响指令执行的理论上限每秒指令数 主频 / CPI指令周期。主频越快 CPU 越快的前提是 CPI 不变但实际性能还受架构、缓存、指令集等因素影响。 4.3.4 软中断 上述外部硬件中断需要硬件设备触发。有没有可能因为软件原因也触发上面的逻辑有为了让操作系统支持进行系统调用CPU也设计了对应的汇编指令(int或者syscall)可以让CPU内部触发中断逻辑。 所以 问题 用户层怎么把系统调用号给操作系统-寄存器比如EAX)操作系统怎么把返回值给用户-寄存器或者用户传入的缓冲区地址系统调用的过程其实就是先intOx80、syscall陷入内核本质就是触发软中断CPU就会自动执行系统调用的处理方法而这个方法会根据系统调用号自动查表执行对应的方法系统调用号的本质数组下标可是为什么我们用的系统调用从来没有见过什么intOx80或者syscall呢都是直接调用上层的函数的啊那是因为Linux的 gnu C 标准库给我们把几乎所有的系统调用全部封装了。 4.3.5 缺页中断内存碎片处理除零野指针错误? 缺页中断内存碎片处理除零野指针错误这些问题全部都会被转换成为CPU内部的软中断然后走中断处理例程完成所有处理。有的是进行申请内存填充页表进行映射的。有的是用来处理内存碎片的有的是用来给目标进行发送信号杀掉进程等等。 所以: 操作系统就是躺在中断处理例程上的代码块CPU内部的软中断比如int 0x80或者syscall我们叫做陷阱CPU内部的软中断比如除零/野指针等我们叫做异常。所以能理解“缺页异常”为什么这么叫了吗 4.4 如何理解内核态和用户态 结论 操作系统无论怎么切换进程都能找到同一个操作系统换句话说系统调用的内核代码在共享的内核地址空间执行但会访问当前进程的用户地址空间资源并使用该进程的内核栈关于特权级别涉及到段段描述符段选择子DPLCPLRPL等概念而现在芯片为了保证兼容性已经非常复杂了进而导致OS也必须得照顾它的复杂性这块我们不做深究了。用户态就是执行用户[0,3]GB时所处的状态内核态就是执行内核[3,4]GB时所处的状态区分就是按照CPU内的CPL决定CPL的全称是Current PrivilegeLevel即当前特权级别。一般执行int0x80或者syscall软中断CPL会在校验之后自动变更 五、可重入函数 5.1 什么是可重入函数 可重入函数是指在多个执行流同时调用时不会产生副作用的函数。其核心特点是 线程安全在多线程环境下被并发调用时不会因共享资源如全局变量导致数据竞争。信号安全在信号处理函数中被调用时不会破坏程序状态如正在执行的操作被中断。 5.2 不可重入的典型场景与风险 不可重入函数在多线程或信号处理中可能引发以下问题 全局变量或静态变量污染 // 不可重入函数示例依赖全局变量 int total 0; int add_to_total(int value) {total value; // 多线程访问时可能导致数据竞争return total; }风险若两个线程同时调用add_to_total可能因线程切换导致计算错误如两个线程各加 1但结果只加了 1。 标准库函数的不可重入性 许多标准库函数依赖静态缓冲区或状态如 strtok()使用静态指针保存分割位置。gmtime()/localtime()返回静态缓冲区的指针。 示例 // 不可重入函数示例使用静态缓冲区 char format_time(void) {time_t now time(NULL);return ctime(now); // ctime()返回静态缓冲区多线程调用会覆盖结果 }信号处理中的不可重入风险 若信号处理函数调用不可重入函数可能导致 主程序正在执行的操作被中断数据结构被破坏。信号处理函数与主程序同时修改共享资源引发竞态条件。 5.3 可重入函数的设计原则 避免共享资源 不使用全局变量或静态变量。若必须使用通过互斥锁如pthread_mutex_t保护。 使用局部变量和栈 所有数据存储在栈上如函数参数、局部变量每个调用独立拥有副本。避免调用不可重入函数 例如 用strtok_r()替代strtok()带_r后缀的通常是可重入版本。用gmtime_r()替代gmtime()。
5.4 可重入函数的实现示例 // 可重入版本使用线程局部存储TLS #include pthread.h// 线程局部变量每个线程独立拥有副本 __thread int thread_total 0;int add_to_total(int value) {thread_total value; // 线程安全每个线程使用自己的副本return thread_total; }// 可重入版本使用互斥锁保护全局变量 #include pthread.hint global_total 0; pthread_mutex_t total_mutex PTHREAD_MUTEX_INITIALIZER;int add_to_total_safe(int value) {pthread_mutex_lock(total_mutex); // 加锁global_total value;pthread_mutex_unlock(total_mutex); // 解锁return global_total; }5.5 五、信号处理中的可重入性 在信号处理函数中仅能调用可重入函数如write()、_exit()避免调用 标准 IO 函数如printf()、fprintf()。内存分配函数如malloc()、free()。浮点运算函数如sin()、cos()。 六、volatile 6.1 volatile 的本质与作用 volatile是 C/C 中的一个类型修饰符用于告诉编译器 不要对变量进行优化如缓存到寄存器或重排序。每次访问变量时都直接从内存读取写入时立即刷新到内存。 其核心作用是确保变量的访问与物理内存直接交互而非编译器的临时缓存。 6.2 信号处理函数中的全局变量 场景在信号处理函数中修改主程序使用的全局变量。原因信号可能在任意时刻触发编译器不能假设变量不变。示例 volatile sig_atomic_t signal_received 0;void signal_handler(int signo) {signal_received 1; // 原子操作确保可见性 }int main() {signal(SIGINT, signal_handler);while (!signal_received) { // 每次检查都从内存读取// 主程序工作…}return 0; }七、SIGCHLD 信号Linux 进程管理的 “子进程通知机制” 7.1 SIGCHLD 信号的本质与作用 SIGCHLD信号编号 17是 Linux 系统中由内核自动发送给父进程的信号用于通知以下事件 子进程终止正常退出或被信号终止。子进程暂停如收到 SIGSTOP 信号。子进程恢复如收到 SIGCONT 信号。 其核心作用是让父进程能够异步处理子进程状态变化避免父进程持续轮询如通过wait()阻塞等待。 7.2 SIGCHLD 的默认行为与问题 默认行为忽略进程收到信号后无动作。潜在问题若父进程未处理 SIGCHLD子进程终止后会变成僵尸进程Zombie Process占用系统资源如进程表项。 7.3 处理 SIGCHLD 的三种方式 忽略信号最简单但有风险 // 忽略SIGCHLD信号子进程终止后直接释放资源 signal(SIGCHLD, SIG_IGN); // 或使用sigaction// 子进程代码 if (fork() 0) {// 子进程执行…exit(0); // 退出后不会变成僵尸进程 }注意Linux 中忽略 SIGCHLD 会让内核自动回收子进程资源但某些 UNIX 系统可能不支持建议使用方式 2 或 3。 捕获信号并调用 wait ()/waitpid () #include signal.h #include sys/wait.hvoid sigchld_handler(int signo) {int status;// 非阻塞等待所有子进程避免wait()阻塞while (waitpid(-1, status, WNOHANG) 0) {// 处理子进程退出状态if (WIFEXITED(status)) {printf(子进程正常退出状态码: %d\n, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf(子进程被信号终止信号: %d\n, WTERMSIG(status));}} }int main() {// 注册信号处理函数struct sigaction sa;sa.sa_handler sigchld_handler;sigemptyset(sa.sa_mask);sa.sa_flags SA_RESTART | SA_NOCLDSTOP; // SA_NOCLDSTOP忽略暂停/恢复信号sigaction(SIGCHLD, sa, NULL);// 创建子进程pid_t pid fork();if (pid 0) {// 子进程执行…sleep(2);exit(42);}// 父进程继续执行…return 0; }关键点 使用waitpid(-1, status, WNOHANG)非阻塞回收多个子进程。SA_RESTART标志避免系统调用被信号中断。SA_NOCLDSTOP忽略子进程暂停 / 恢复事件仅关注终止。 使用 sigaction 的 SA_NOCLDWAIT 标志现代方式 struct sigaction sa; sa.sa_handler SIG_IGN; // 或自定义处理函数 sigemptyset(sa.sa_mask); sa.sa_flags SA_NOCLDWAIT; // 内核自动回收子进程不产生僵尸 sigaction(SIGCHLD, sa, NULL);优势内核自动释放子进程资源无需手动调用wait()。 7.4 调试与监控 查看僵尸进程 ps aux | grep Z # 显示状态为Z的僵尸进程跟踪信号处理 strace -e signal your_program # 跟踪信号处理系统调用7.5 常见误区与注意事项 竞态条件 若父进程未捕获 SIGCHLD可能导致 子进程已终止但父进程未及时回收变成僵尸。父进程调用wait()时子进程尚未终止导致阻塞。 信号丢失 非实时信号如 SIGCHLD不排队若多个子进程同时终止可能只收到一个信号。需在处理函数中循环调用waitpid()回收所有子进程。与 fork ()/exec () 的关系 fork()创建的子进程继承父进程的 SIGCHLD 处理方式。exec()后子进程保留 SIGCHLD 的处理方式除非设置了SA_RESETHAND。
7.6 总结 SIGCHLD 的核心价值 提供异步机制让父进程感知子进程状态变化避免轮询或阻塞等待。最佳实践 优先使用SA_NOCLDWAIT自动回收子进程。若需获取子进程状态在信号处理函数中循环调用waitpid()。 应用场景 守护进程如 init 进程管理所有子进程。多进程服务器如 Web 服务器 fork 子进程处理请求。
- 上一篇: 外贸电商网站开发网页设计html代码大全
- 下一篇: 外贸发货做网站怎么写创保网app下载
相关文章
-
外贸电商网站开发网页设计html代码大全
外贸电商网站开发网页设计html代码大全
- 技术栈
- 2026年04月20日
-
外贸出口工艺品怎么做外贸网站大连旅游攻略
外贸出口工艺品怎么做外贸网站大连旅游攻略
- 技术栈
- 2026年04月20日
-
外贸soho怎么建网站建设网站的方法
外贸soho怎么建网站建设网站的方法
- 技术栈
- 2026年04月20日
-
外贸发货做网站怎么写创保网app下载
外贸发货做网站怎么写创保网app下载
- 技术栈
- 2026年04月20日
-
外贸公司 如何做公司网站苏州电商关键词优化
外贸公司 如何做公司网站苏州电商关键词优化
- 技术栈
- 2026年04月20日
-
外贸公司如何做公司网站医院网站建设标书
外贸公司如何做公司网站医院网站建设标书
- 技术栈
- 2026年04月20日
