【Linux】详解用户态和内核态&&内核中信号被处理的时机&&sigaction信号自定义处理方法

简介: 【Linux】详解用户态和内核态&&内核中信号被处理的时机&&sigaction信号自定义处理方法

一、用户态和内核态的理解

       在操作系统中,用户态和内核态是两种主要的执行模式,它们代表了不同的访问级别和权限,用于确保系统的安全和稳定性。

1.1、用户态

       用户态是操作系统中用户进程的运行状态。在这种状态下,进程只能访问受限的系统资源,并且不能执行某些特权操作。用户态下的进程没有权限直接访问硬件或执行某些敏感的系统调用。它们必须通过系统调用接口来请求内核态的服务。

1.2、内核态

      内核态是操作系统的核心部分(即内核)的运行状态。在这种状态下,代码可以访问系统内的所有内存空间,并且可以执行特权指令。内核态下的代码具有最高级别的权限,可以访问硬件、执行敏感操作,并管理系统资源。

1.3、用户态和内核态的切换时机

       当用户态下的进程需要执行特权操作时,它会通过系统调用接口向内核发出请求。这时,操作系统会保存用户态的上下文,然后切换到内核态来执行相应的服务。除了系统调用外,如硬件中断、软件异常也会导致系统从用户态切换到内核态。在这三种情况下,操作系统都会保存用户态的上下文,并在内核态下处理这些事件。

二、信号被处理的时机

      如下图所示,进程从内核态切换回用户态的时候,信号会被检测并处理。如果该信号的处理方法为默认处理方法,就不需要从内核态切换回用户态来处理我们写的方法,但如果该信号的处理方法为我们自己的自定义处理方法,就要从内核态切换回用户态来执行,执行完毕程序再进入内核。下面是进程处理信号并执行我们写的自定义处理方法的过程:

       执行信号自定义的处理方法时,可以由操作系统直接帮我们做了,但是为了避免我们写的处理方法存在非法操作,就必须切换回用户态, 由用户态的权限来约束我们是否能执行这个处理方法

三、用户态和内核态切换的内核级理解

在我们的进程地址空间中存在1G的内核空间,这部分会用来映射加载到内存中的操作系统。

        在内核中,所有的系统调用函数其实是被用一个函数指针数组所管理起来的,该函数指针数组会通过页表的映射与1G的内核空间的某些虚拟地址建立起映射关系,这样当正文代码部分要调用某个系统调用函数时,只需要拿着虚拟地址在内核空间中寻找再通过页表的映射就可以在内存中找到该系统调用函数。

       在系统中会存在多个进程,每个进程的地址空间的[0,3G]的地址空间映射的内容都不相同,但是,每个进程都要进行系统调用 ,也就意味着地址空间中的1G内核级空间所映射的内容可以完全相同。进一步说,我们的进程无论如何总能找到操作系统,我们的进程访问操作系统,都是通过进程地址空间中[3GB,4GB]这一部分空间来访问的。

       既然操作系统已经被映射到我们进程的地址空间上,那是否意味着我们可以随便访问操作系统中的内容了呢?答案肯定不是的。在CPU中有寄存器可以标识当前进程的状态(比如说00就表示处于用户态,11就表示处于内核态),如果进程处于用户态就阻止进程访问操作系统

四、设置自定义信号处理的函数

设置自定义信号处理的函数除了signal函数外,还有一个sigaction函数。

      第一个参数为几号信号,第二个参数为一个描述新方法的结构体, 第三个参数为一个描述旧方法的结构体。

       该结构体中第一个成员变量为新设置的信号处理方法,第二个参数为类似于sa_handler,但它提供了更强大的功能,因为该函数还可以接收有关信号发送者的信息,可以设置为nullptr,第三个参数为一个信号集,可以用来设置在处理signum信号的同时所屏蔽的信号,第四个参数用于修改信号处理的某些默认行为,第五个参数通常不用于现代操作系统,不设置。 下面是一个sigaction函数使用的例子。

自定义2号信号的处理方法,让进程不断打印pending位图:

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
 
void print(sigset_t& pending)
{
    for(int i = 31; i>= 1; i--)
    {
        if(sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";
    }
    cout << endl;
}
 
//自定义2号信号的处理方法,让进程不断打印pending位图
void handler(int sig)
{
    sigset_t pending;
    sigemptyset(&pending);
 
    while(true)
    {
        sigpending(&pending);
        print(pending);
        sleep(1);
    }
}

再让进程收到2号信号的同时阻塞3,4,5号信号:

int main()
{
    struct sigaction act,oact;
    act.sa_handler = handler;
    act.sa_flags = 0;
 
    cout << getpid() << endl;
 
    //清空要添加的信号集
    sigemptyset(&act.sa_mask);
    //让进程收到2号信号的同时阻塞3,4,5号信号
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);
 
    sigaction(2, &act, &oact);
 
    while(true)
    {}
    return 0;
}

最终运行结果:

       第一次给进程发送2号信号,信号被执行了所以pending位图中没有2号信号,再次发送2号信号发现pending位图中有2号信号,证明2号信号被屏蔽了,因为进程在处理某个信号时如果再次给它发送同一个信号该信号会被自动屏蔽,依次再向进程发送3,4,5号信号,发现信号都被屏蔽了。

相关文章
|
2天前
|
算法 安全 Linux
探索Linux内核的虚拟内存管理
【5月更文挑战第20天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分之一——虚拟内存管理。通过剖析其关键组件和运作机制,揭示虚拟内存如何提供高效的内存抽象,支持庞大的地址空间,以及实现内存保护和共享。文章将重点讨论分页机制、虚拟内存区域(VMAs)的管理、页面置换算法,并简要分析这些技术是如何支撑起现代操作系统复杂而多变的内存需求的。
|
1天前
|
算法 Linux 调度
【进程调度】Linux内核的进程调度队列--runqueue
【进程调度】Linux内核的进程调度队列--runqueue
|
1天前
|
缓存 网络协议 算法
Linux内核必读五本书籍(强烈推荐)
Linux内核必读五本书籍(强烈推荐)
24 0
|
8天前
|
NoSQL Ubuntu Linux
【操作系统】实验三 编译 Linux 内核
【操作系统】实验三 编译 Linux 内核
11 1
|
8天前
|
Linux Windows 编译器
|
8天前
|
Unix Linux C语言
|
8天前
|
存储 算法 Linux
【Linux】线程的内核级理解&&详谈页表以及虚拟地址到物理地址之间的转化
【Linux】线程的内核级理解&&详谈页表以及虚拟地址到物理地址之间的转化
|
9月前
|
Linux
Linux驱动程序开发用户态和内核态 模块机制
Linux驱动程序开发用户态和内核态 模块机制
|
消息中间件 缓存 Linux
Linux 用户态与内核态的交互——netlink 篇
理论篇 在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通读,也在最新版本中改变为netlink,无疑,它将是Linux用户态与内核态交流的主要方法之一。它的通信依据是一个对应于进程的标识,一般定为该进程的 ID。当通信的一端处于中断过程时,该标识为 0。当使用 netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。但通信双方有一端是中断过程,使用方法则
600 0
|
Unix Shell Linux
Linux探秘之用户态与内核态
从宏观上来看,Linux 操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。
6619 0