精华内容
下载资源
问答
  • 内核态与用户态

    2021-07-01 00:48:42
    多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 Linux 内核态无法对传统的 Linux进程间同步和通信的方法提供足够的支持。本文总结并比较了几种内核态与用户态进程通信的实现方法,并推荐使用 netlink套...

    多数的 Linux 内核态程序都需要和用户空间的进程交换数据,但 Linux 内核态无法对传统的 Linux

    进程间同步和通信的方法提供足够的支持。本文总结并比较了几种内核态与用户态进程通信的实现方法,并推荐使用 netlink

    套接字实现中断环境与用户态进程通信。

    Linux

    是一个源码开放的操作系统,无论是普通用户还是企业用户都可以编写自己的内核代码,再加上对标准内核的裁剪从而制作出适合自己的操作系统。目前有很多中低

    端用户使用的网络设备的操作系统是从标准 Linux 改进而来的,这也说明了有越来越多的人正在加入到 Linux 内核开发团体中。

    一个或多个内核模块的实现并不能满足一般 Linux

    系统软件的需要,因为内核的局限性太大,如不能在终端上打印,不能做大延时的处理等等。当我们需要做这些的时候,就需要将在内核态采集到的数据传送到用户

    态的一个或多个进程中进行处理。这样,内核态与用户空间进程通信的方法就显得尤为重要。在 Linux

    的内核发行版本中没有对该类通信方法的详细介绍,也没有其他文章对此进行总结,所以本文将列举几种内核态与用户态进程通信的方法并详细分析它们的实现和适

    用环境。

    在一台运行 Linux 的计算机中,CPU 在任何时候只会有如下四种状态:

    【1】 在处理一个硬中断。

    【2】 在处理一个软中断,如 softirq、tasklet 和 bh。

    【3】 运行于内核态,但有进程上下文,即与一个进程相关。

    【4】 运行一个用户态进程。

    其中,【1】、【2】和【3】是运行于内核空间的,而【4】是在用户空间。其中除了【4】,其他状态只可以被在其之上的状态抢占。比如,软中断只可以被硬中断抢占。

    Linux 内核模块是一段可以动态在内核装载和卸载的代码,装载进内核的代码便立即在内核中工作起来。Linux 内核代码的运行环境有三种:用户上下文环境、硬中断环境和软中断环境。但三种环境的局限性分两种,因为软中断环境只是硬中断环境的延续。比较如表【1】。

    内核态环境

    介绍

    局限性

    用户上下文

    内核态代码的运行与一用户空间进程相关,如系统调用中代码的运行环境。

    不可直接将本地变量传递给用户态的内存区,因为内核态和用户态的内存映射机制不同。

    硬中断和软中断环境

    硬中断或软中断过程中代码的运行环境,如 IP 数据报的接收代码的运行环境,网络设备的驱动程序等。

    不可直接向用户态内存区传递数据;代码在运行过程中不可阻塞。

    Linux 传统的进程间通信有很多,如各类管道、消息队列、内存共享、信号量等等。但它们都无法介于内核态与用户态使用,原因如表【2】。

    通信方法

    无法介于内核态与用户态的原因

    管道(不包括命名管道)

    局限于父子进程间的通信。

    消息队列

    在硬、软中断中无法无阻塞地接收数据。

    信号量

    无法介于内核态和用户态使用。

    内存共享

    需要信号量辅助,而信号量又无法使用。

    套接字

    在硬、软中断中无法无阻塞地接收数据。

    运行

    在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和 UNIX

    域套接字来实现内核态与用户态的通信。但这些方法的数据传输效率较低,Linux 内核提供

    copy_from_user()/copy_to_user()

    函数来实现内核态与用户态数据的拷贝,但这两个函数会引发阻塞,所以不能用在硬、软中断中。一般将这两个特殊拷贝函数用在类似于系统调用一类的函数中,此

    类函数在使用中往往"穿梭"于内核态与用户态。此类方法的工作原理路如图【1】。

    image001.gif

    其中相关的系统调用是需要用户自行编写并载入内核。imp1.tar.gz

    是一个示例,内核模块注册了一组设置套接字选项的函数使得用户空间进程可以调用此组函数对内核态数据进行读写。源码包含三个文件,imp1.h

    是通用头文件,定义了用户态和内核态都要用到的宏。imp1_k.c 是内核模块的源代码。imp1_u.c

    是用户态进程的源代码。整个示例演示了由一个用户态进程向用户上下文环境发送一个字符串,内容为"a message from

    userspace\n"。然后再由用户上下文环境向用户态进程发送一个字符串,内容为"a message from kernel\n"。

    比起用户上下文环境,硬中断和软中断环境与用户态进程无丝毫关系,而且运行过程不能阻塞。

    3.2.1 使用一般进程间通信的方法

    我们无法直接使用传统的进程间通信的方法实现。但硬、软中断中也有一套同步机制--自旋锁(spinlock),可以通过自旋锁来实现中断环境与中

    断环境,中断环境与内核线程的同步,而内核线程是运行在有进程上下文环境中的,这样便可以在内核线程中使用套接字或消息队列来取得用户空间的数据,然后再

    将数据通过临界区传递给中断过程。基本思路如图【2】。

    image002.gif

    因为中断过程不可能无休止地等待用户态进程发送数据,所以要通过一个内核线程来接收用户空间的数据,再通过临界区传给中断过程。中断过程向用户空间

    的数据发送必须是无阻塞的。这样的通信模型并不令人满意,因为内核线程是和其他用户态进程竞争CPU接收数据的,效率很低,这样中断过程便不能实时地接收

    来自用户空间的数据。

    3.2.2 netlink 套接字

    在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,同时还使用

    netlink 实现了 ip queue 工具,但 ip queue 的使用有其局限性,不能自由地用于各种中断过程。内核的帮助文档和其他一些

    Linux 相关文章都没有对 netlink 套接字在中断过程和用户空间通信的应用上作详细的说明,使得很多用户对此只有一个模糊的概念。

    netlink 套接字的通信依据是一个对应于进程的标识,一般定为该进程的 ID。当通信的一端处于中断过程时,该标识为 0。当使用

    netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。但通信双方有一端是中断过程,使用方法则不同。netlink

    套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函

    数。工作原理如图【3】。

    image003.gif

    很明显,这里使用了软中断而不是内核线程来接收数据,这样就可以保证数据接收的实时性。

    当 netlink 套接字用于内核空间与用户空间的通信时,在用户空间的创建方法和一般套接字使用类似,但内核空间的创建方法则不同。图【4】是 netlink 套接字实现此类通信时创建的过程。

    image004.gif

    以下举一个 netlink 套接字的应用示例。示例实现了从 netfilter 的 NF_IP_PRE_ROUTING 点截获的 ICMP 数据报,在将数据报的相关信息传递到一个用户态进程,由用户态进程将信息打印在终端上。源码在文件imp2.tar.gz中。内核模块代码(分段详解):

    static struct sock *nlfd;

    struct

    {

    __u32 pid;

    rwlock_t lock;

    }user_proc;

    /*挂接在 netfilter 框架的 NF_IP_PRE_ROUTING 点上的函数为 get_icmp()*/

    static struct nf_hook_ops imp2_ops =

    {

    .hook = get_icmp,/*netfilter 钩子函数*/

    .pf = PF_INET,

    .hooknum = NF_IP_PRE_ROUTING,

    .priority = NF_IP_PRI_FILTER -1,

    };

    static int __init init(void)

    {

    rwlock_init(&user_proc.lock);

    /*在内核创建一个 netlink socket,并注明由 kernel_recieve() 函数接收数据

    这里协议 NL_IMP2 是自定的*/

    nlfd = netlink_kernel_create(NL_IMP2, kernel_receive);

    if(!nlfd)

    {

    printk("can not create a netlink socket\n");

    return -1;

    }

    /*向 netfilter 的 NF_IP_PRE_ROUTING 点挂接函数*/

    return nf_register_hook(&imp2_ops);

    }

    static void __exit fini(void)

    {

    if(nlfd)

    {

    sock_release(nlfd->socket);

    }

    nf_unregister_hook(&imp2_ops);

    }

    module_init(init);

    module_exit(fini);

    其实片断(一)的工作很简单,模块加载阶段先在内核空间创建一个 netlink 套接字,再将一个函数挂接在 netfilter 框架的 NF_IP_PRE_ROUTING 钩子点上。卸载时释放套接字所占的资源并注销之前在 netfilter 上挂接的函数。

    DECLARE_MUTEX(receive_sem);

    01:static void kernel_receive(struct sock *sk, int len)

    02:{

    03: do

    04: {

    05: struct sk_buff *skb;

    06: if(down_trylock(&receive_sem))

    07: return;

    08:

    09: while((skb = skb_dequeue(&sk-10: {

    11: {

    12: struct nlmsghdr *nlh = NULL;

    13:if(skb-14: {

    15: nlh = (struct nlmsghdr *)skb-16: if((nlh-17: && (skb-18: {

    19: if(nlh-20: {

    21: write_lock_bh(&user_proc.pid);

    22: user_proc.pid = nlh-23: write_unlock_bh(&user_proc.pid);

    24: }

    25: else if(nlh-26: {

    27: write_lock_bh(&user_proc.pid);

    28: if(nlh-29: write_unlock_bh(&user_proc.pid);

    30: }

    31: }

    32: }

    33: }

    34: kfree_skb(skb);

    35: }

    36: up(&receive_sem);

    37: }while(nlfd && nlfd-38:}

    如果读者看过 ip_queue.c 或 rtnetlink.c中的源码会发现片断(二)中的 03~18 和 31~38 是

    netlink socket 在内核空间接收数据的框架。在框架中主要是从套接字缓存中取出全部的数据,然后分析是不是合法的数据报,合法的

    netlink 数据报必须有nlmsghdr

    结构的报头。在这里笔者使用了自己定义的消息类型:IMP2_U_PID(消息为用户空间进程的ID),IMP2_CLOSE(用户空间进程关闭)。因为

    考虑到 SMP,所以在这里使用了读写锁来避免不同 CPU 访问临界区的问题。kernel_receive() 函数的运行在软中断环境。

    static unsigned int get_icmp(unsigned int hook,

    struct sk_buff **pskb,

    const struct net_device *in,

    const struct net_device *out,

    int (*okfn)(struct sk_buff *))

    {

    struct iphdr *iph = (*pskb)->nh.iph;

    struct packet_info info;

    if(iph->protocol == IPPROTO_ICMP)/*若传输层协议为 ICMP*/

    {

    read_lock_bh(&user_proc.lock);

    if(user_proc.pid != 0)

    {

    read_unlock_bh(&user_proc.lock);

    info.src = iph->saddr;/*记录源地址*/

    info.dest = iph->daddr;/*记录目的地址*/

    send_to_user(&info);/*发送数据*/

    }

    else

    read_unlock_bh(&user_proc.lock);

    }

    return NF_ACCEPT;

    }

    static int send_to_user(struct packet_info *info)

    {

    int ret;

    int size;

    unsigned char *old_tail;

    struct sk_buff *skb;

    struct nlmsghdr *nlh;

    struct packet_info *packet;

    size = NLMSG_SPACE(sizeof(*info));

    /*开辟一个新的套接字缓存*/

    skb = alloc_skb(size, GFP_ATOMIC);

    old_tail = skb->tail;

    /*填写数据报相关信息*/

    nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));

    packet = NLMSG_DATA(nlh);

    memset(packet, 0, sizeof(struct packet_info));

    /*传输到用户空间的数据*/

    packet->src = info->src;

    packet->dest = info->dest;

    /*计算经过字节对其后的数据实际长度*/

    nlh->nlmsg_len = skb->tail - old_tail;

    NETLINK_CB(skb).dst_groups = 0;

    read_lock_bh(&user_proc.lock);

    ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT); /*发送数据*/

    read_unlock_bh(&user_proc.lock);

    return ret;

    nlmsg_failure: /*若发送失败,则撤销套接字缓存*/

    if(skb)

    kfree_skb(skb);

    return -1;

    }

    /*字节对齐*/

    #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

    /*计算包含报头的数据报长度*/

    #define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))

    /*字节对齐后的数据报长度*/

    #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

    /*填写相关报头信息,这里使用了nlmsg_failure标签,所以在程序中要定义*/

    #define NLMSG_PUT(skb, pid, seq, type, len) \

    ({ if (skb_tailroom(skb) < (int)NLMSG_SPACE(len)) goto nlmsg_failure; \

    __nlmsg_put(skb, pid, seq, type, len); })

    static __inline__ struct nlmsghdr *

    __nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len)

    {

    struct nlmsghdr *nlh;

    int size = NLMSG_LENGTH(len);

    nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size));

    nlh->nlmsg_type = type;

    nlh->nlmsg_len = size;

    nlh->nlmsg_flags = 0;

    nlh->nlmsg_pid = pid;

    nlh->nlmsg_seq = seq;

    return nlh;

    }

    /*跳过报头取实际数据*/

    #define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

    /*取 netlink 控制字段*/

    #define NETLINK_CB(skb)(*(struct netlink_skb_parms*)&((skb)->cb))

    运行示例时,先编译 imp2_k.c 模块,然后使用 insmod 将模块加载入内核。再运行编译好的 imp2_u

    命令,此时就会显示出本机当前接收的 ICMP 数据报的源地址和目的地址。用户可以使用 Ctrl+C

    来终止用户空间的进程,再次启动也不会带来问题。

    本文从内核态代码的不同运行环境来实现不同方法的内核空间与用户空间的通信,并分析了它们的实际效果。最后推荐使用 netlink 套接字实现中断环境与用户态进程通信,因为 netlink 套接字是专为此类通信定制的。

    展开全文
  • 在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。如果所有的程序都能使用这些指令,那么你的...每一个用户进程运行时都好像有一份内核的拷贝,每当用户进程使...

     

    在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。如果所有的程序都能使用这些指令,那么你的系统一天死机n回就不足为奇了。所以,CPU将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通的应用程序只能使用那些不会造成灾难的指令。Intel的CPU将特权级别分为4个级别:RING0,RING1,RING2,RING3。

        linux的内核是一个有机的整体。每一个用户进程运行时都好像有一份内核的拷贝,每当用户进程使用系统调用时,都自动地将运行模式从用户级转为内核级,此时进程在内核的地址空间中运行。

        当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。

        内核态与用户态是操作系统的两种运行级别,跟intel cpu没有必然的联系, 如上所提到的intel cpu提供Ring0-Ring3四种级别的运行模式,Ring0级别最高,Ring3最低。Linux使用了Ring3级别运行用户态,Ring0作为 内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。Linux进程的4GB地址空间,3G-4G部 分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运 行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必 须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能 随意操作内核地址空间,具有一定的安全保护作用。

         处理器总处于以下状态中的一种:

    1、内核态,运行于进程上下文,内核代表进程运行于内核空间;

    2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;

    3、用户态,运行于用户空间。

     

    从用户空间到内核空间有两种触发手段:

    1.用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

    2.硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。

       一个程序我们可以从两种角度去分析。其一就是它的静态结构,其二就是动态过程。下图表示了用户态和内核态直接的关系(静态的角度来观察程序)

     

     

    (1)用户态和内核态的概念?

    --->内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
    --->用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取

    (2)为什么需要用户态和内核态?

    --->由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 :用户态 和 内核态

    (3)用户态与内核态的切换?

    所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作.

    这时需要一个这样的机制: 用户态程序切换到内核态, 但是不能控制在内核态中执行的指令

    这种机制叫系统调用, 在CPU中的实现称之为陷阱指令(Trap Instruction)

    他们的工作流程如下:

    1. 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务.
    2. 用户态程序执行陷阱指令
    3. CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问
    4. 这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数, 并执行程序请求的服务
    5. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果
    二:用户态和内核态的详细介绍?

    当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核 代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行 态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程 的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。 

    内核态与用户态是操作系统的两种运行级别, 跟intel cpu没有必然的联系, intel cpu提供Ring0-Ring3三种级别的运行模式,Ring0级别最高,Ring3最低。Linux使用了Ring3级别运行用户态,Ring0作为 内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。Linux进程的4GB地址空间,3G-4G部 分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运 行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必 须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能 随意操作内核地址空间,具有一定的安全保护作用。
    至于说保护模式,是说通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程的地址空间中的数据。

     

    1. 用户态和内核态的概念区别

    究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的重点和着眼的角度放在了实现的功能和代码的逻辑性上,先看一个例子:

    1)例子

    C代码  收藏代码
    1. void testfork(){  
    2. if(0 = = fork()){  
    3. printf(“create new process success!\n”);  
    4. }  
    5. printf(“testfork ok\n”);  
    6. }  

         这段代码很简单,从功能的角度来看,就是实际执行了一个fork(),生成一个 新的进程,从逻辑的角度看,就是判断了如果fork()返回的是0则打印相关语句,然后函数最后再打印一句表示执行完整个testfork()函数。代码 的执行逻辑和功能上看就是如此简单,一共四行代码,从上到下一句一句执行而已,完全看不出来哪里有体现出用户态和进程态的概念。

          如果说前面两种是静态观察的角度看的话,我们还可以从动态的角度来看这段代码,即它被转换成CPU执行的指令后加载执行的过程,这时这段程序就是一个动态执行的指令序列。而究竟加载了哪些代码,如何加载就是和操作系统密切相关了。

    2)特权级

         熟悉Unix/Linux系统的人都知道,fork的工作实际上是以系统调用的 方式完成相应功能的,具体的工作是由sys_fork负责实施。其实无论是不是Unix或者Linux,对于任何操作系统来说,创建一个新的进程都是属于 核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪 个程序就能去做,于是就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可以做到集中管理,减少有限资源的访问和使用冲 突。

          特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很 多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有 CPL、DPL和RPL,这里不再过多阐述。硬件已经提供了一套特权级使用的相关机制,软件自然就是好好利用的问题,这属于操作系统要做的事情,对于 Unix/Linux来说,只使用了0级特权级和3级特权级。也就是说在Unix/Linux系统中,一条工作在0级特权级的指令具有了CPU能提供的最 高权力,而一条工作在3级特权级的指令具有CPU提供的最低或者说最基本权力。

    3)用户态和内核态

          现在我们从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级 特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运 行在0级特权级上时,就可以称之为运行在内核态。

          虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不 同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,比如上面例子中的testfork()就不能直接调用 sys_fork(),因为前者是工作在用户态,属于用户态程序,而sys_fork()是工作在内核态,属于内核态程序。

          当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系 统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发 sys_fork()的执行时,就切换到了内核态。

     

    2. 用户态和内核态的转换

    1)用户态切换到内核态的3种方式

    a. 系统调用

         这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使 用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户 特别开放的一个中断来实现,例如Linux的int 80h中断。

    b. 异常

          当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

    c. 外围设备的中断

          当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会 暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到 内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

     

          这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

     

    2)具体的切换操作

          从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态 到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而 异常和中断的处理机制基本上也是一致的,关于它们的具体区别这里不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析,涉及到由用户态切换到内核态 的步骤主要包括:

    [1] 从当前进程的描述符中提取其内核栈的ss0及esp0信息。

    [2] 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个

    过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一

    条指令。

    [3] 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始

    执行中断处理程序,这时就转到了内核态的程序执行了。

     

    原文链接:[http://wely.iteye.com/blog/2332327]

    展开全文
  • 用户态和内核态区别

    2021-02-09 14:02:03
    操作系统用户态和内核态之间的切换过程 1. 用户态和内核态的概念区别 究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的...

    操作系统用户态和内核态之间的切换过程

    1. 用户态和内核态的概念区别

     

    究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的重点和着眼的角度放在了实现的功能和代码的逻辑性上,先看一个例子:

    1)例子

       void testfork(){
    
           if(0 = = fork()){
    
           printf(“create new process success!/n”);
    
    }
    
    
    
    printf(“testfork ok/n”);
    
    }

     

           这段代码很简单,从功能的角度来看,就是实际执行了一个fork(),生成一个新的进程,从逻辑的角度看,就是判断了如果fork()返回的是0则打印相关语句,然后函数最后再打印一句表示执行完整个testfork()函数。代码的执行逻辑和功能上看就是如此简单,一共四行代码,从上到下一句一句执行而已,完全看不出来哪里有体现出用户态和进程态的概念。

     

    如果说前面两种是静态观察的角度看的话,我们还可以从动态的角度来看这段代码,即它被转换成CPU执行的指令后加载执行的过程,这时这段程序就是一个动态执行的指令序列。而究竟加载了哪些代码,如何加载就是和操作系统密切相关了。

     

    2)特权级

     

    熟悉Unix/Linux系统的人都知道,fork的工作实际上是以系统调用的方式完成相应功能的,具体的工作是由sys_fork负责实施。其实无论是不是Unix或者Linux,对于任何操作系统来说,创建一个新的进程都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪个程序就能去做,于是就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可以做到集中管理,减少有限资源的访问和使用冲突。

     

    特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有CPL、DPL和RPL,这里不再过多阐述。硬件已经提供了一套特权级使用的相关机制,软件自然就是好好利用的问题,这属于操作系统要做的事情,对于Unix/Linux来说,只使用了0级特权级和3级特权级。也就是说在Unix/Linux系统中,一条工作在0级特权级的指令具有了CPU能提供的最高权力,而一条工作在3级特权级的指令具有CPU提供的最低或者说最基本权力。

    3)用户态和内核态

     

            现在我们从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在0级特权级上时,就可以称之为运行在内核态。

     

    虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,比如上面例子中的testfork()就不能直接调用sys_fork(),因为前者是工作在用户态,属于用户态程序,而sys_fork()是工作在内核态,属于内核态程序。

     

    当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发sys_fork()的执行时,就切换到了内核态。

     

     

     

    2.   用户态和内核态的转换

     

    1)用户态切换到内核态的3种方式

     

    a. 系统调用

     

            这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。

     

    b. 异常

     

            当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

     

    c. 外围设备的中断

     

            当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

     

     

     

    这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

     

     

     

    2)具体的切换操作

     

    从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,关于它们的具体区别这里不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析,涉及到由用户态切换到内核态的步骤主要包括:

     

    [1] 从当前进程的描述符中提取其内核栈的ss0及esp0信息。

     

    [2] 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。

     

    [3] 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。

    举个例子:

     

    下面的图中执行了连续的两条指令: mov eax, 0x4

     

                                                                      int 0x80

     

    可以看到,指令mov eax, 0x4对应的cs为0x7,eip为0x308a,ss为0xf,esp为0x80320

     

    这表明此条指令的特权级是3级,也就是说该指令是工作在用户态下的。

     

    int 0x80即所谓的系统调用执行的软中断,此条指令执行完之后,cs变换成了0x8,eip为0x1400,ss变成了0x10, esp 变成了0x1ffffec。这时系统已经转入了内核态执行,而且栈也变为了内核栈。

     

    内核态程序执行完毕时如果要从内核态返回用户态,可以通过执行指令iret来完成,指令iret会将先前压栈的进入内核态前的cs,eip,eflags,ss,esp信息从栈里弹出,加载到各个对应的寄存器中,重新开始执行用户态的程序,这个过程不再详述。

    展开全文
  • 内核态和用户态

    2021-07-18 12:37:19
    为了区分不同的程序的不同权限,人们发明了内核态和用户态的概念。 **那么什么是内核态,什么又是用户态呢?**只要想一想现实生活中处于社会核心的人与处于社会边缘的人有什么区别,就能明白处于

    就像世界上的人并不平等一样,并不是所有的程序都是平等的。世界上有的人占有资源多,有的人占有资源少,有的人来了,别人得让出资源,有的人则专门为别人让出资源。程序也是这样的,有的程序可以访问计算机的任何资源,有的程序则只能访问少量受限资源。操作系统作为计算机的管理者,自然不能和被管理者享受一样的待遇。它应该享有更多的方便或权限。为了区分不同的程序的不同权限,人们发明了内核态和用户态的概念

    **那么什么是内核态,什么又是用户态呢?**只要想一想现实生活中处于社会核心的人与处于社会边缘的人有什么区别,就能明白处于核心的人拥有的资源多!因此,内核态就是拥有资源多的状态,或者说访问资源多的状态,称为特权态。相对来说,用户态就是非特权态,在此种状态下访问的资源将受到限制。如果一个程序运行在特权态,则该程序就可以访问计算机的任何资源,即它的资源访问不受限制。如果一个程序运行在用户态,则其资源需求将受到各种限制。

    例如,如果要访问操作系统的内核数据结构,如进程表,则需要在特权态下才能办到。如果要访问用户程序里的数据,则在用户态就可以了。

    由于内核态的程序可以访问计算机的所有资源,因此这种程序的可靠性和安全性就显得十分重要。试想如果一个不可靠的程序在内核态下修改了操作系统的各种内核数据结构,结果会怎样呢?整个系统有可能崩溃。运行于用户态的程序就比较简单,如果其可靠性和安全性出了问题,其造成的损失只不过是让用户程序崩溃,而操作系统将继续运行。

    很显然,内核态和用户态各有优势:运行在内核态的程序可以访问的资源多,但可靠性、安全性要求高,维护管理都较复杂;用户态程序程序访问的资源有限,但可靠性、安全性要求低,自然编写维护起来比较简单。一个程序到底应该运行在内核态还是用户态则取决于其对资源和效率的需求

    一般来说,如果一个程序能够运行于用户态,就应该让它运行在用户态。只在迫不得已的情况下,才让程序运行于内核态。凡是牵扯到计算机本体根本运行的事情都应该在内核态下执行,只与用户数据和应用相关的东西则放在用户态执行。另外,对时序要求特别高的操作,也应该在内核态完成。

    那么什么样的功能应该在内核态下实现呢?首先,CPU的管理和内存管理都应该在内核态实现。这些功能可不可以在用户态下实现呢?当然能,但是不太安全。从保障计算机安全的角度来说,CPU和内存的管理必须在内核态实现。

    诊断与测试程序也需要在内核态下实现。因为诊断和测试需要访问计算机的所有资源,否则怎么判断计算机是否正常呢?就像中医治病,必须把脉触摸病人。你不让中医触摸,他怎么能看病呢(当然,很多人认为中医是伪科学,根本治不了病,本书对此问题不做讨论)?输入输出管理也一样,因为要访问各种设备和底层数据结构,所以也必须在内核态实现。

    对于文件系统来说,则可以一部分放在用户态,一部分放在内核态。文件系统本身的管理,即文件系统的宏数据部分的管理必须放在内核态,否则任何人都可能破坏文件系统的结构;用户数据的管理则可以放在用户态。编译器、网络管理的部分功能、编辑器、用户程序等,自然都可以放在用户态下执行。图3-8描述的是Windows操作系统的内核态与用户态的界线。

    在这里插入图片描述

    态势的识别

    那么计算机是如何知道现在正在运转的程序是内核态程序呢?而正确做出内核态或用户态的判断对系统的正确运行至关重要。显然做出这种判断需要某种标志。这个标志就是处理器的一个状态位。这个状态位是CPU状态字里面的一个字位。也就是说,所谓的用户态、内核态实际上是处理器的一种状态,而不是程序的状态。我们通过设置该状态字,可以将CPU设置为内核态、用户态或者其他的子态(有的CPU有更多种子态)。一个程序运行时,CPU是什么态,这个程序就运行在什么态

    内核态与用户态的实现

    前面说过,内核态是特权态,而用户态是普通态。特权态下运行的程序可以访问任何资源,而用户态下的访问则受到限制。那么这种限制是如何实现的呢?

    显然,要限制一个程序对资源的访问,需要对程序执行的每一条指令进行检查才能完成。而这种检查就是地址翻译。程序发出的每一条指令都要经过这个地址翻译过程。而通过对翻译的控制,就可以限制程序对资源的访问。

    为了赋予内核态程序访问所有资源的权限,当系统处于内核态时,内核程序可以绕过内存地址翻译而直接执行特权指令,如停机指令。这种绕过翻译的做法突破了系统对资源的控制。

    展开全文
  • 1. 内核态和用户态、内核线程和用户线程等解释操作系统调度CPU的最小单元是线程,也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且...
  • 本篇文章开始探秘用户态与内核态,虽然一般面试不会问这个,但搞清楚这块,对我们理解整个计算机系统是及其有意义的,这会让你在今后的学习中豁然开朗,你肯定会发出:“啊,原来如此的感叹!” 内容大纲 小故事 ...
  • kernel:linux-4.9 cpu: ARMV8 背景 在广袤的代码中堆栈无疑是一个高热度的技术用语,就linux而言你能常观察到的几个场景有: 用户堆栈 函数func_foo中用堆栈来保存寄存器、局部变量等等:
  • 用户态和内核态切换

    千次阅读 2020-12-22 21:30:27
    从用户态到内核态切换可以通过三种方式,或者说会导致从用户态切换到内核态的操作: 系统调用,这个上面已经讲解过了,在我公众号之前的文章也有讲解过。其实系统调用本身就是中断,但是软件中断,跟硬中断不同。...
  • 用户态、内核态,进程、线程这些概念经常是脱口而出,但是并没有特别理解这些概念背后的含义。比方说,对于java的多线程,有的地方说java多线程线程切换的时候会涉及用户态切换到内核态、进程上线文切换、线程上下文...
  • Linux总体架构图咱们先...这些系统调用组成了用户态跟内核态交互的基本接口,例如:用户态想要申请一块20K大小的动态内存,就须要brk系统调用,将数据段指针向下偏移,若是用户态多处申请20K动态内存,同时又释放呢...
  • 用户态线程和内核态线程有什么区别? 什么是用户态和内核态? 系统调用过程 线程模型 用户态线程 内核态线程 用户态线程和内核态线程之间的映射关系 总结 用户态线程和内核态线程有什么区别? 这是一个组合...
  • 关注我们,设为星标,每天7:00不见不散,每日java干货分享 内核态,用户态以典型 JAVA / LINUX 为例解释:CPU 为了更好的 管理内存 并区分对 CPU指令的执行权限,对程...
  • 系统环境:基于linux 2.6.32.27 和 linux 3.16.36Linux内核态和用户态进程通信方法的提出和实现用户上下文环境运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和Unix域套接字来实现内核态和...
  • 用户态和内核态的区别是,内核态运行操作系统程序,操作硬件,用户态运行用户程序;当程序运行在3级特权级上时,可以称之为运行在用户态,当程序运行在0级特权级上时,称之为运行在内核态。本文操作环境:windows10...
  • 无论是内核态还是用户态函数最终都会执行 do_execve() 内核态 sys_execve,在 Linux 0.11 源码中,0.11/include/linux/sys.h extern int sys_execve(); fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_...
  • Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。用户态与内核态内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活...
  • 原标题:初探Linux内核态——通过proc文件系统作快速问题定位本文是对一篇blog的翻译,感谢译者Hualet Wang。原文通过一个例子为我们展示了,在分析进程运行缓慢的问题时,strace和pstack都束手无策的情况下,不要忘...
  • 一、用户态、内核态内核空间是共享的,存在整个内核的代码和所有的内核模块以及内核所维护的数据。进程在运行时一般会处于两种状态:用户态,内核态。用户态是指进程在用户代码中运行。内核态是指进程进入内核代码,...
  • 1. 用户态和内核态的概念区别究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的重点和着眼的角度放在了实现的功能和代码的逻辑...
  • 内核态与用户态:代表了cpu的两种工作状态 1.内核态:操作系统运行程序,所以可以操作计算机硬件工作。 2.用户态:应用程序运行程序,所以无法操作计算机硬件工作。 C P U 指令集权限 在说用户态与内核态...
  • 一、进程内核栈、用户栈 1.进程的堆栈 ????task_struct和内核栈的位置,以及为什么放在一起,详见https://developer.aliyun.com/article/369052 内核在创建进程的时候,在创建task_struct的同时,会为进程创建...
  • 经过编译,synchronized标注的函数会加一个读写锁,一般不推荐使用,因为加锁解锁设计到内核态与用户态的转换,有时转化耗时比函数体执行时间还长,所以不推荐使用。 这个观点在学操作系统的时候作为常识被认知,那...
  • 用户态与内核态的区别与理解 先给大家看看Linux进程的地址空间,如下: 对于Linux内核态的地址空间,3G-4G是大家共享的。这里存放的是整个内核的代码和所有的内核模块以及内核所维护的数据。 1、为什么需要区分内核...
  • java用户态和内核态

    2021-03-08 15:01:26
    在这本书上多次看到用户态和内核态两个名词, 虽然大概能明白意思. 但对于两者具体的定义和区别还是比较, 特此查阅之后记录.内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个...
  • 内核态用户态是什么? 操作系统对程序的执行权限进行分级,分别为用户态和内核态。用户态相比内核态有较低的执行权限,很多操作是不被操作系统允许的,简单来说就是用户态只能访问内存,防止程序错误影响到其他程序,而...
  • Linux 用户态和内核态由于 CPU 权限的限制,通信并不像想象中的使用进程间通信方式那么简单,今天这篇文章就来看看 Linux 用户态和内核态究竟有哪些通信方式。我们平常在写代码时,一般是在用户空间,通过系统调用...
  • 浅谈操作系统的用户态和内核态我们经常会听到程序的用户态和内核态,一个程序从用户态进入了内核态。。。什么是用户态和内核态内核态和用户态到底指的是什么呢?我们这就解开其神秘面纱所谓的用户态、内核态,实际上...
  • 在“RDMA之Verbs”一文中我们说道,Verbs API分为用户态和内核态,分别以ibv_和ib_作为前缀。RDMA技术的最大的优势就在于用户态可以绕过内核,直接控制硬件收发数据,减少了系统调用和内存拷贝次数,所以大部分的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 137,683
精华内容 55,073
关键字:

内核态