情公司做的网站/友链提交入口

情公司做的网站,友链提交入口,建设个人网站赚钱的经历,网站设计和备案目录 一、信号处理概述:为什么需要“信号”? 二、用户空间与内核空间:进程的“双重人格” 三、内核态与用户态:权限的“安全锁” 四、信号捕捉的内核级实现:层层“安检” 五、sigaction函数:精细控制信…

目录

一、信号处理概述:为什么需要“信号”?

二、用户空间与内核空间:进程的“双重人格”

三、内核态与用户态:权限的“安全锁”

四、信号捕捉的内核级实现:层层“安检”

五、sigaction函数:精细控制信号行为

1. 函数原型

2. 关键结构体

3. 示例代码:动态修改信号处理

六、可重入函数

1. 问题场景

2. 问题分析

3. 原因解释

4. 不可重入函数与可重入函数

5. 如何避免重入问题

七、volatile

1. 问题引入

2. 未使用volatile的情况

3. 使用volatile解决问题

4. volatile的作用总结


一、信号处理概述:为什么需要“信号”?

        想象你在办公室工作时,突然有人敲门提醒你快递到了。这里的“敲门”就像操作系统发给进程的信号。信号是操作系统通知进程某个事件发生的机制,例如:

  • Ctrl+C 发送 SIGINT 信号终止进程

  • 程序崩溃时内核发送 SIGSEGV 信号

  • 用户自定义信号处理逻辑(如保存日志)

        但进程不会立即处理信号,而是在“合适的时候”——比如从内核态切换回用户态时。这背后隐藏着操作系统的核心设计逻辑。


二、用户空间与内核空间:进程的“双重人格”

每个进程的地址空间分为两部分:

用户空间内核空间
存储进程私有代码和数据存储操作系统全局代码和数据
通过用户级页表映射物理内存通过内核级页表映射物理内存
每个进程看到的内容不同所有进程看到的内容相同

// 示例:用户空间的变量
int user_data = 100; // 内核空间的代码(进程无权直接访问)
void kernel_code() 
{// 管理硬件资源 
}

关键点

  • 用户态代码无法直接访问内核空间(权限不足)

  • 执行系统调用(如printf)时,进程会陷入内核,切换到内核态


三、内核态与用户态:权限的“安全锁”

用户态内核态
权限等级低(普通用户代码)高(操作系统代码)
操作限制无法直接访问硬件可执行任何指令
触发场景执行普通代码系统调用、中断、异常

状态切换示例

printf("Hello");  // 用户态 -> 内核态(执行write系统调用) -> 用户态

具体步骤

1、用户态:调用printf

  • printf是C标准库函数,负责格式化字符串(如将"Hello"转换为字符流)。
  • 若输出到终端(如屏幕),最终会调用**系统调用write**将数据写入文件描述符(如标准输出stdout)。

2、触发系统调用write

系统调用是用户程序请求操作系统服务的唯一入口。

write的函数签名:

ssize_t write(int fd, const void *buf, size_t count);

其中fd=1表示标准输出,buf指向数据缓冲区,count为数据长度。

3、从用户态陷入内核态

  • CPU执行特殊的陷入指令(如syscallint 0x80),触发软中断。
  • 硬件自动切换特权级:用户态(ring 3)→ 内核态(ring 0)。
  • 跳转到内核中预定义的系统调用处理函数(如sys_write)。

4、内核态:执行sys_write

  • 操作系统验证参数合法性(如fd是否有效)。
  • 将用户空间的数据("Hello")从缓冲区复制到内核空间(防止用户篡改)。
  • 调用设备驱动,将数据发送到终端(如控制台、SSH会话)。
  • 记录返回结果(成功写入的字节数或错误码)。

5、返回用户态

  • 内核恢复用户程序的寄存器状态和堆栈。
  • CPU特权级切换回用户态(ring 3)。
  • 用户程序继续执行printf之后的代码。

🌴 为什么需要切换特权态?

  • 用户态的限制
    用户程序无法直接访问硬件(如磁盘、网卡)或修改关键数据结构(如进程表)。
    例:若允许用户程序直接写磁盘,恶意程序可能覆盖系统文件。

  • 内核态的权限
    操作系统代码拥有最高权限,可安全管理硬件和资源。
    通过系统调用“代理”用户程序的请求,确保所有操作受控。


四、信号捕捉的内核级实现:层层“安检”

        在计算机系统里,程序运行时可能会遇到一些特殊情况,比如用户按下某些按键或者系统出现了问题,这时候就需要程序能够及时做出反应。这种反应机制在Linux系统中是通过“信号”来实现的。信号就像是一个信使,负责把发生的事件告诉程序。

        现在,假设一个程序正在运行它的主函数(main函数),就好比一个人正在按照计划做一件大事。突然,某个特定的事件发生了,比如用户按下了一个特殊的按键组合(这会触发SIGQUIT信号)。这时候,系统会暂时中断这个人的工作,切换到一个专门处理这种情况的模式,也就是“内核态”,由操作系统来处理这个事件。

        操作系统在处理完这个事件后,准备回到原来的程序继续工作之前,会检查有没有需要特别处理的信号。如果发现有SIGQUIT信号,而且这个程序之前已经告诉过操作系统,当这个信号出现时要按照它自己定义的方式来处理(也就是注册了一个信号处理函数sighandler),那么操作系统就会安排一个特殊的操作。

        这个操作就是:不是直接回到原来的主函数继续做之前的事情,而是先去执行那个专门定义的处理函数sighandler。这就好比在你做一件大事的时候,突然有紧急情况需要你先去处理一下,处理完了再回来继续做原来的事。

        需要注意的是,这个处理函数sighandler和原来的主函数(main函数)是两个完全独立的任务,它们就像两条平行的路,没有直接的调用关系。sighandler有自己的工作空间(不同的堆栈空间)来完成它的任务。

        当处理函数sighandler完成自己的任务后,它会触发一个特殊的指令(sigreturn系统调用),再次回到操作系统那里。操作系统会检查是否还有其他的紧急情况需要处理。如果没有,就会回到原来的主函数,恢复之前的状态,继续完成未做完的事情。

当进程从内核态返回用户态时,会检查未决信号集(pending)

  1. 检查信号状态

    • 若信号未被阻塞(block),且处理动作为默认忽略
      → 立即处理(如终止进程)并清除pending标志

    • 若处理动作为自定义
      → 先返回用户态执行处理函数,再通过sigreturn回到内核

  2. 执行自定义处理函数的关键步骤

    • 内核不信任用户代码:必须返回用户态执行处理函数

    • 处理函数与主流程独立(不同堆栈,无调用关系)


五、sigaction函数:精细控制信号行为

1. 函数原型

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);

参数说明:

  • signo :指定信号的编号(如SIGINT)。

  • act :新的处理动作

  • oldact :保存旧的处理动作

2. 关键结构体

结构体 sigaction 的定义如下:

struct sigaction 
{void     (*sa_handler)(int);          // 信号处理函数sigset_t   sa_mask;                   // 额外屏蔽的信号int        sa_flags;                  // 控制选项(通常设为0)// 其他字段(如sa_sigaction)暂不讨论
};

3. 示例代码:动态修改信号处理

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>struct sigaction act, oact;// 自定义信号处理函数
void handler(int signo)
{printf("捕获信号: %d\n", signo);// 恢复为默认处理方式(仅第一次捕获时自定义)sigaction(SIGINT, &oact, NULL);
}int main()
{memset(&act, 0, sizeof(act));memset(&oact, 0, sizeof(oact));act.sa_handler = handler;  // 设置自定义处理函数act.sa_flags = 0;          // 无特殊标志sigemptyset(&act.sa_mask); // 不额外屏蔽其他信号// 注册SIGINT信号(Ctrl+C触发)sigaction(SIGINT, &act, &oact);while (1){printf("程序运行中...\n");sleep(1);}return 0;
}

运行效果

  1. 第一次按下Ctrl+C → 打印“捕获信号: 2”

  2. 再次按下Ctrl+C → 进程终止(已恢复默认行为)


六、可重入函数

1. 问题场景

        假设我们有一个简单的链表结构,定义了两个节点 node1 和 node2,以及一个头指针 head。在 main 函数中,我们调用 insert 函数将 node1 插入到链表中。insert 函数的实现分为两步:首先将新节点的 next 指针指向当前头节点,然后更新头指针为新节点。

node_t node1, node2, *head;void insert(node_t *p) {p->next = head; // 第一步:将新节点的 next 指针指向当前头节点head = p;       // 第二步:更新头指针为新节点
}int main() {// ... 其他代码 ...insert(&node1); // 在 main 函数中插入 node1// ... 其他代码 ...
}

        在插入 node1 的过程中,假设刚执行完第一步(p->next = head),此时发生了硬件中断,导致进程切换到内核态。在内核态处理完中断后,检查到有信号待处理,于是切换到信号处理函数 sighandler。sighandler 同样调用 insert 函数,试图将 node2 插入到同一个链表中。

void sighandler(int signo) {// ... 其他代码 ...insert(&node2); // 在信号处理函数中插入 node2// ... 其他代码 ...
}

        当 sighandler 完成插入 node2 的操作并返回内核态后,再次回到用户态,继续执行 main 函数中被中断的 insert 函数的第二步(head = p)。

2. 问题分析

        理想情况下,我们希望 main 函数和 sighandler 分别将 node1 和 node2 插入到链表中,最终链表包含两个节点。然而,实际情况却并非如此。

  1. main 函数插入 node1 的第一步 :将 node1 的 next 指针指向当前头节点(初始时 head 为 NULL),此时 node1->next = NULL。

  2. 中断发生,切换到内核态 :main 函数的 insert 操作被中断,此时 head 还未更新为 node1。

  3. sighandler 插入 node2 :在信号处理函数中,执行 insert(&node2)。此时 head 仍为 NULL,所以 node2->next = NULL,然后 head 被更新为 node2。

  4. 返回 main 函数继续执行 :执行 insert 函数的第二步,将 head 更新为 node1。

        最终,链表的头指针 head 指向 node1,而 node1 的 next 指针为 NULL。node2 被插入后又被覆盖,实际上没有真正加入链表。

3. 原因解释

        这个问题的根源在于 insert 函数被不同的控制流程(main 函数和 sighandler)调用,且在第一次调用还未完成时就再次进入该函数。这种现象称为“重入”(Reentrant)。insert 函数访问了一个全局链表 head,由于全局变量在多个控制流程之间共享,导致数据不一致。

4. 不可重入函数与可重入函数

  1. 不可重入函数 :如果一个函数在被调用过程中,其内部操作依赖于全局变量或共享资源,并且在函数执行过程中这些资源可能被其他调用者修改,那么这个函数就是不可重入的。像上面的 insert 函数,因为它操作了全局链表 head,所以在重入情况下容易出错。

  2. 可重入函数 :如果一个函数只访问自己的局部变量或参数,不依赖于全局变量或共享资源,那么它就是可重入的。可重入函数在不同控制流程中被调用时,不会相互干扰。

5. 如何避免重入问题

  1. 避免使用全局变量 :尽量使用局部变量,或者通过参数传递必要的数据。

  2. 使用互斥机制 :在多线程或信号处理场景中,使用互斥锁(如 mutex)来保护共享资源的访问。

  3. 设计可重入函数 :确保函数只依赖于参数和局部变量,不依赖于外部环境。


七、volatile

        在C语言中,volatile 是一个经常被提及但又容易被误解的关键字。今天,我们通过一个具体的信号处理例子,来深入理解 volatile 的作用。

1. 问题引入

考虑以下代码:

#include <stdio.h>
#include <signal.h>int flag = 0;void handler(int sig) {printf("change flag 0 to 1\n");flag = 1;
}int main() {signal(2, handler);while (!flag);printf("process quit normal\n");return 0;
}

        该程序的功能是:在接收到 SIGINT 信号(如用户按下 Ctrl+C)时,执行自定义信号处理函数 handler,将全局变量 flag 设置为 1,从而退出 while 循环,程序正常结束。

2. 未使用volatile的情况

        在未使用 volatile 修饰 flag 的情况下,编译器可能会对代码进行优化。例如,当使用 -O2(大写字母O) 优化选项编译时,编译器可能会认为 flag 的值在 while 循环中不会被改变(因为从代码的静态分析来看,没有明显的修改操作),于是将 flag 的值缓存到 CPU 寄存器中,而不是每次都从内存中读取。

        这就会导致一个问题:当信号处理函数 handler 修改了 flag 的值时,while 循环中的条件判断仍然使用寄存器中的旧值,无法及时检测到 flag 的变化,程序无法正常退出。这种现象被称为“数据不一致性”或“内存可见性”问题。

3. 使用volatile解决问题

为了解决上述问题,我们需要使用 volatile 关键字修饰 flag 变量:

#include <stdio.h>
#include <signal.h>volatile int flag = 0;void handler(int sig) {printf("change flag 0 to 1\n");flag = 1;
}int main() {signal(2, handler);while (!flag);printf("process quit normal\n");return 0;
}

   volatile 告诉编译器,该变量的值可能会被程序之外的其他因素(如信号处理函数、硬件中断等)改变,因此编译器在优化时不会假设该变量的值不变。每次访问 volatile 修饰的变量时,编译器都会生成代码从内存中重新读取该变量的值,而不是使用寄存器中的缓存值。

        这样,在信号处理函数修改了 flag 的值后,while 循环中的条件判断能够及时检测到变化,程序可以正常退出。

4. volatile的作用总结

volatile 的主要作用是保持内存的可见性,确保程序能够正确地读取和写入变量的最新值。在以下场景中,使用 volatile 是必要的:

  1. 信号处理 :当变量可能被信号处理函数修改时,需要使用 volatile 修饰,以确保主程序能够及时检测到变量的变化。

  2. 多线程编程 :在多线程环境中,当变量可能被其他线程修改时,volatile 可以防止编译器优化导致的内存可见性问题。不过,需要注意的是,volatile 并不能完全替代互斥锁等同步机制,因为它不能保证操作的原子性。

  3. 硬件寄存器访问 :当程序需要直接访问硬件寄存器时,这些寄存器的值可能会被硬件异步修改,因此需要使用 volatile 修饰相关的指针或变量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/898409.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【HarmonyOS Next】鸿蒙中App、HAP、HAR、HSP概念详解

【HarmonyOS Next】鸿蒙中App、HAP、HAR、HSP概念详解 &#xff08;图1-1&#xff09; 一、鸿蒙中App、HAP、HAR、HSP是什么&#xff1f; &#xff08;1&#xff09;App Pack&#xff08;Application Package&#xff09; 是应用发布的形态&#xff0c;上架应用市场是以App Pa…

在C#的MVC框架framework项目的使用ajax,及源码下载

在C# MVC框架中使用AJAX实现异步请求,有助于提高应用程序的性能和用户体验。 在MVC框架framework项目中&#xff0c;ajax使用方法如下 1.在Controller类中&#xff0c;创建一个新的方法&#xff08;例如&#xff1a;GetRes&#xff09;&#xff0c;该方法处理AJAX请求并返回J…

Linux部署DHCP服务脚本

#!/bin/bash #部署DHCP服务 #userli 20250319#检查是否为root用户 if[ "$USER" ! "root" ] thenecho "错误&#xff1a;非root用户&#xff0c;权限不足&#xff01;"exit 0 fi#配置网络环境 read -ep "请给本机配置一个IP地址(不…

vulhub Matrix-Breakout

1.下载靶机&#xff0c;打开靶机和kali虚拟机 2.查询kali和靶机ip 3.浏览器访问 访问81端口有登陆界面 4.扫描敏感目录 kali dirb 扫描 一一访问 robot.txt提示我们继续找找&#xff0c;可能是因为我们的字典太小了&#xff0c;我们换个扫描器换个字典试下,利用kali自带的最大…

深度学习项目--基于DenseNet网络的“乳腺癌图像识别”,准确率90%+,pytorch复现

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 如果说最经典的神经网络&#xff0c;ResNet肯定是一个&#xff0c;从ResNet发布后&#xff0c;很多人做了修改&#xff0c;denseNet网络无疑是最成功的…

面试八股 —— Redis篇

重点&#xff1a;缓存 和 分布式锁 缓存&#xff08;穿透&#xff0c;击穿&#xff0c;雪崩&#xff09; 降级可作为系统的保底策略&#xff0c;适用于穿透&#xff0c;击穿&#xff0c;雪崩 1.缓存穿透 2.缓存击穿 3.缓存雪崩 缓存——双写一致性 1.强一致性业务&#xff08…

瑞萨RA系列使用JLink RTT Viewer输出调试信息

引言 还在用UART调试程序么?试试JLINK的RTT Viewer吧!不需占用UART端口、低资源暂用、实时性高延时微秒级,这么好的工具还有什么理由不用了! 目录 一、JLink RTT Viewer 简介 二、软件安装 三、工程应用 3.1 SEGGER_RTT驱动包 3.2 手搓宏定义APP_PRINT 3.3 使用APP_…

MySQL 入门大全:查询语言分类

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

1.Windows+vscode+cline+MCP配置

文章目录 1.简介与资源2.在windows中安装vscode及Cline插件1. 安装vscode2. 安装Cline插件3. 配置大语言模型3. 配置MCP步骤(windows) 1.简介与资源 MCP官方开源仓库 MCP合集网站 参考视频 2.在windows中安装vscode及Cline插件 1. 安装vscode 2. 安装Cline插件 Cline插件…

性能测试过程实时监控分析

性能监控 前言一、查看性能测试结果的3大方式1、GUI界面报告插件2、命令行运行 html报告3、后端监听器接入仪表盘 二、influxDB grafana jmeter测试监控大屏1、原理&#xff1a;2、linux环境中influxDB 安装和配置3、jmerer后端监听器连接influxDB4、linux环境总grafana环境搭…

【Linux我做主】浅谈Shell及其原理

浅谈Linux中的Shell及其原理 Linux中Shell的运行原理github地址前言一、Linux内核与Shell的关系1.1 操作系统核心1.2 用户与内核的隔离 二、Shell的演进与核心机制2.1 发展历程2.2 核心功能解析2.3 shell的工作流程1. 用户输入命令2. 解析器拆分指令3. 扩展器处理动态内容变量替…

可视化图解算法:链表中倒数(最后)k个结点

1. 题目 描述 输入一个长度为 n 的链表&#xff0c;设链表中的元素的值为ai &#xff0c;返回该链表中倒数第k个节点。 如果该链表长度小于k&#xff0c;请返回一个长度为 0 的链表。 数据范围&#xff1a;0≤n≤105&#xff0c;0 ≤ai≤109&#xff0c;0 ≤k≤109 要求&am…

在线教育网站项目第四步:deepseek骗我, WSL2不能创建两个独立的Ubuntu,但我们能实现实例互访及外部访问

一、说明 上一章折腾了半天&#xff0c;搞出不少问题&#xff0c;今天我们在deepseek的帮助下&#xff0c;完成多个独立ubuntu24.04实例的安装&#xff0c;并完成固定ip&#xff0c;实践证明&#xff0c;deepseek不靠谱&#xff0c;浪费我2个小时时间&#xff0c;我们将在下面实…

Spring Cloud之负载均衡之LoadBalance

目录 负载均衡 问题 步骤 现象 什么是负载均衡&#xff1f; 负载均衡的一些实现 服务端负载均衡 客户端负载均衡 使用Spring Cloud LoadBalance实现负载均衡 负载均衡策略 ​编辑 ​编辑LoadBalancer原理 服务部署 准备环境和数据 服务构建打包 启动服务 上传J…

数据无忧:自动备份策略全解析

引言 在信息化飞速发展的今天&#xff0c;数据已成为个人、企业乃至国家最为宝贵的资产之一。无论是日常办公文档、科研数据、客户资料&#xff0c;还是个人隐私信息&#xff0c;一旦丢失或损坏&#xff0c;都可能带来不可估量的损失。因此&#xff0c;备份文件作为数据安全的…

Latex2024安装教程(附安装包)Latex2024详细图文安装教程

文章目录 前言一、Latex2024下载二、Texlive 2024安装教程1.准备安装文件2.启动安装程序3.配置安装选项4.开始安装5.安装完成6.TeX Live 2024 安装后确认 三、Texstudio 安装教程1.准备 Texstudio 安装2.启动 Texstudio 安装向导3.选择安装位置4.等待安装完成5.启动 Texstudio6…

C++ 语法之函数和函数指针

在上一章中 C 语法之 指针的一些应用说明-CSDN博客 我们了解了指针变量&#xff0c;int *p;取变量a的地址这些。 那么函数同样也有个地址&#xff0c;直接输出函数名就可以得到地址&#xff0c;如下&#xff1a; #include<iostream> using namespace std; void fun() …

关于“碰一碰发视频”系统的技术开发文档框架

以下是关于“碰一碰发视频”系统的技术开发文档框架&#xff0c;涵盖核心功能、技术选型、开发流程和关键模块设计&#xff0c;帮助您快速搭建一站式解决方案 --- 随着短视频平台的兴起&#xff0c;用户的创作与分享需求日益增长。而如何让视频分享更加便捷、有趣&#xff0c…

基于django+vue的购物商城系统

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.8数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 系统首页 热卖商品 优惠资讯 个人中心 后台登录 管理员功能界面 用户管理 商品分类管理…

Ardunio 连接OLED触摸屏(SSD1106驱动 4针 IIC通信)

一、准备工作 1、硬件 UNO R3 &#xff1a;1套 OLED触摸屏&#xff1a;1套 导线诺干 2、软件 arduino 二、接线 UNO R3OLED5VVCCGNDGNDA5SCLA4SDA 脚位如下图所示&#xff1a; Uno R3脚位图 触摸屏脚位图 查阅显示屏的驱动规格&#xff1a;通常显示屏驱动芯片有SSD1306,SH110…