扩展阅读
  • linux c/c++ IP字符串转换成可比较大小的数字
  • 在win分区上安装linux和独立分区安装linux有什么区别?可以同时安装吗?(两个linux系统)
  • linux哪个版本好?linux操作系统版本详细介绍及选择方案推荐
  • 在虚拟机上安装的linux上,能像真的linux系统一样开发linux程序么?
  • secureCRT下Linux终端汉字乱码解决方法
  • 我重装window后,把linux的引导区覆盖了,进不了linux怎么办?急啊,望热心的人帮助 (现在有linux的盘)
  • Linux c字符串中不可打印字符转换成16进制
  • 红旗Linux主机可以通过127.0.0.1访问,但如何是连网的Win2000机器通过Linux的IP去访问Linux
  • Linux常用命令介绍:更改所属用户群组或档案属性
  • 安装vmware软件,不用再安装linux系统,就可以模拟linux系统了,然后可以在其上学习一下LINUX下的基本操作 了?
  • linux命令大全详细分类介绍及常用linux命令文档手册下载
  • 我重装window后,把linux的引导区覆盖了,进不了linux怎么办?急啊,望热心的人帮助 (现在没有linux的盘,只有DOS启动盘)
  • Linux Kernel 'sctp_v6_xmit()'函数信息泄露漏洞
  • 如何让win2000和linux共存。我装好WIN2000,再装LINUX7.0,但LILO只能找到LINUX,不能引导WIN2000
  • linux c下利用srand和rand函数生成随机字符串
  • 在windows中的VMware装了个linux,主板有两个串口,能做windows和linux的串口通信测试么,怎么测试这两个串口在linux是有效
  • Linux c++虚函数(virtual function)简单用法示例代码
  • 我们网站的服务器从windows2000迁往linux,ASP程序继续使用,可是我连LINUX的皮毛都不了解,大家告诉我LINUX下怎么建网站??
  • Docker官方镜像将会使用Alpine Linux替换Ubuntu
  • 中文Linux与西文Linus分别哪一个版是权威?I认为是:中科软的白旗Linux与西文的绿帽子Linux!大家的看法呢?
  • Linux下chmod命令详细介绍及用法举例
  • Windows2000和Linux双操作系统,Linux系统有问题,我直接把Linux分区删除后,Windows2000进不去了,怎么办???
  •  
    当前位置:  操作系统>Linux

    Linux进程的内核栈和用户栈概念,相互关系及切换过程

     
        发布时间:2013-11-28  


        本文导语:  什么是进程的堆栈 每个进程都有自己的堆栈,内核在创建一个新的进程时,在创建进程控制块task_struct的同时,也为进程创建自己堆栈。一个进程 有2个堆栈,用户堆栈和系统堆栈;用户堆栈的空间指向用户地址空间...

    什么是进程的堆栈

        每个进程都有自己的堆栈,内核在创建一个新的进程时,在创建进程控制块task_struct的同时,也为进程创建自己堆栈。一个进程 有2个堆栈,用户堆栈和系统堆栈;用户堆栈的空间指向用户地址空间,内核堆栈的空间指向内核地址空间。当进程在用户态运行时CPU堆栈指针寄存器指向的 用户堆栈地址,使用用户堆栈,当进程运行在内核态时,CPU堆栈指针寄存器指向的是内核栈空间地址,使用的是内核栈;

    用户和内核栈的区别

        内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。记住,进程对应的用户栈和内核栈都是进程私有的。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。有些系统中专门为全局中断处理提供了中断栈,但是x86中并没有中断栈,中断在当前进程的内核栈中处理。

    用户态、内核态之间的共享

      我们知道linux的虚拟地址空间是内核态使用3G以上的高地址空间,那么所有的用户进程是如何共享这一个内核空间的呢?

      1)Linux系统中的init进程(pid=1)是除了idle进程(pid=0,也就是init_task)之外另一个比较特殊的进程,它是Linux内核开始建立起进程概念时第一个通过kernel_thread产生的进程,其开始在内核态执行,然后通过一个系统调用,开始执行用户空间的/sbin/init程序,期间Linux内核也经历了从内核态到用户态的特权级转变,/sbin/init极有可能产生出了shell,然后所有的用户进程都有该进程派生出来。而linux采用2级页表(1K x 1K x 4K),页目录的1/4(3G/4G)即256B是属于内核的;所以创建用户进程时会复制init进程的这256B的页目录以及后面的一级、二级页表,也即实现了内核空间的共享。

     2)一个进程在内核态 可以直接通过虚拟地址访问其他进程内核态的数据,因为他们是一个页表。一个进程在内核态 不可以直接通过虚拟地址访问其他进程的用户态的数据,因为他们不使用同一个页表。

     3)由于系统中只有一个内核实例在运行,因此所有进程都映射到单一内核地址空间。内核中维护全局数据结构和每个进程的一些对象信息,后者包括的信息使得内核可以访问任何进程的地址空间。通过地址转换机制进程可以直接访问当前进程的地址空间(通过MMU),而通过一些特殊的方法也可以访问到其它进程的地址空间。

    4)内核态与用户态的交互举个特例:当系统调用的参数超过6个时,将借助寄存器将所要传递给内核的参数包装成一个结构体,并将结构体指针放到指定寄存器。

    进程用户栈和内核栈之间的切换

         当进程由于中断或系统调用从用户态转换到内核态时,进程所使用的栈也要从用户栈切换到内核栈。系统调用实质就是通过指令产生中断,称为软中断。进程因为中断(软中断或硬件产生中断),使得CPU切换到特权工作模式,此时进程陷入内核态,进程进入内核态后,首先把用户态的堆栈地址保存在内核堆栈中,然后设置堆栈指针寄存器的地址为内核栈地址,这样就完成了用户栈向内核栈的切换。当进程从内核态切换到用户态时,最后把保存在内核栈中的用户栈地址恢复到CPU栈指针寄存器即可,这样就完成了内核栈向用户栈的切换。

         这里要理解一下内核堆栈。前面我们讲到,进程从用户态进入内核态时,需要在内核栈中保存用户栈的地址。那么进入内核态时,从哪里获得内核栈的栈指针呢?要解决这个问题,先要理解从用户态刚切换到内核态以后,进程的内核栈总是空的。这点很好理解,当进程在用户空间运行时,使用的是用户 栈;当进程在内核态运行时,内核栈中保存进程在内核态运行的相关信息,但是当进程完成了内核态的运行,重新回到用户态时,此时内核栈中保存的信息全部恢复,也就是说,进程在内核态中的代码执行完成回到用户态时,内核栈是空的。理解了从用户态刚切换到内核态以后,进程的内核栈总是空的,那刚才这个问题就很好理解了,因为内核栈是空的,那当进程从用户态切换到内核态后,把内核栈的栈顶地址设置给CPU的栈指针寄存器就可以了。

    X86 Linux内核栈定义如下(可能现在的版本有所改变,但不妨碍我们对内核栈的理解):

    在/include/linux/sched.h中定义了如下一个联合结构:

    union task_union {
           struct task_struct task;
           unsigned long stack[2408];
    };

    从这个结构可以看出,内核栈占8kb的内存区。实际上,进程的task_struct结构所占的内存是由内核动态分配的,更确切地说,内核根本不给task_struct分配内存,而仅仅给内核栈分配8K的内存,并把其中的一部分给task_struct使用。这样内核栈的起始地址就是union task_union变量的地址+8K 字节的长度。例如:我们动态分配一个union task_union类型的变量如下:

    unsigned char *gtaskkernelstack

    gtaskkernelstack = kmalloc(sizeof(union task_union));

    那么该进程每次进入内核态时,内核栈的起始地址均为:(unsigned char *)gtaskkernelstack + 8096

        那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们

    是如何知道内核栈的地址的呢?

        关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。

    进程上下文

       进程切换现场称为进程上下文(context),包含了一个进程所具有的全部信息,一般包括:进程控制块(Process Control Block,PCB)、有关程序段和相应的数据集进程控制块是进程在内存中的静态存在方式,Linux内核中用task_struct表示一个进程(相当于进程的人事档案)。进程的静 态描述必须保证一个进程在获得CPU并重新进入运行态时,能够精确的接着上次运行的位置继续进行,相关的程序段,数据以及CPU现场信息必须保存。处理机 现场信息主要包括处理机内部寄存器和堆栈等基本数据。进程控制块一般可以分为进程描述信息、进程控制信息,进程相关的资源信息和CPU现场保护机构。

    进程的切换

       当一个进程的时间片到时,进程需要让出CPU给其他进程运行,内核需要进行进程切换。Linux 的进程切换是通过调用函数进程切换函数schedule来实现的。进程切换主要分为2个步骤:

    1. 调用switch_mm()函数进行进程页表的切换;

    2. 调用 switch_to() 函数进行 CPU寄存器切换;

    __switch_to定义在/arch/arm/kernel目录下的entry-armv.S 文件中,源码如下:

    ENTRY(__switch_to)
     UNWIND(.fnstart    )
     UNWIND(.cantunwind )
        add ip, r1, #TI_CPU_SAVE
        ldr r3, [r2, #TI_TP_VALUE]
        stmia   ip!, {r4 - sl, fp, sp, lr} @ Store most regs on stack
    #ifdef CONFIG_MMU
        ldr r6, [r2, #TI_CPU_DOMAIN]
    #endif
    #if __LINUX_ARM_ARCH__ >= 6
    #ifdef CONFIG_CPU_32v6K
        clrex
    #else
        strex   r5, r4, [ip]            @ Clear exclusive monitor
    #endif
    #endif
    #if defined(CONFIG_HAS_TLS_REG)
        mcr p15, 0, r3, c13, c0, 3      @ set TLS register
    #elif !defined(CONFIG_TLS_REG_EMUL)
        mov r4, #0xffff0fff
        str r3, [r4, #-15]          @ TLS val at 0xffff0ff0
    #endif
    #ifdef CONFIG_MMU
        mcr p15, 0, r6, c3, c0, 0       @ Set domain register
    #endif
        mov r5, r0
        add r4, r2, #TI_CPU_SAVE
        ldr r0, =thread_notify_head
        mov r1, #THREAD_NOTIFY_SWITCH
        bl atomic_notifier_call_chain
        mov r0, r5
        ldmia   r4, {r4 - sl, fp, sp, pc}   @ Load all regs saved previously
     UNWIND(.fnend      )
    ENDPROC(__switch_to)

    Switch_to的处理流程如下:

    1. 保存本进程的CPU寄存器(PC、R0 ~ R13)到本进程的栈中;

    2. 保存SP(本进程的栈基地址)到task->thread.save 中;

    3. 从新进程的task->thread.save恢复SP为新进程的栈基地址;

    4. 从新进程的栈中恢复新进程的CPU相关寄存器值,

    5. 新进程开始运行,完成任务切换。

    中断,异常和系统调用发生时具体执行过程

      //-----中断,异常,系统调用 : 开始

      1)在用户空间发生中断时,CPU会自动在内核空间保存用户堆栈的SS, 用户堆栈的ESP, EFLAGS, 用户空间的CS, EIP, 中断号 - 256

      | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 - 256

      进入内核后,会进行一个SAVE_ALL,这样内核栈上的内容为:

      | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 中断号 - 256 | ES | DS | EAX | EBP | EDI | ESI | 方法 iis7站长之家 | ECX | EBX

      好了,一切都处理完时,内核jmpRESTORE_ALL(它是一个,例:在x86_32体系结构下,/usr/src/kernel/arch/286/kernel/entry_32.S文件里包含该宏的定义)

      RESTORE做的工作,从它的代码里就可以看出来了:  

      首先把栈上的 ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX pop到对应的寄存器里

      然后将esp + 4 把 “中断号 - 256” pop掉

      此时内核栈上的内容为:

      | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP

      最后执行iret指令,此时CPU会从内核栈上取出SS, ESP, ELFGAS, CS, EIP,然后接着运行。

      2) 在用户空间发生异常时,CPU自动保存在内核栈的内容为:

      | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 出错代码 error_code

      (注:CPU只是在进入异常时才知道是否应该把出错代码压入堆栈(为什么?),而从异常处理通过iret指令返回时已经时过境迁,CPU已经无从知当初发生异常的原因,因此不会自动跳过这一项,而要靠相应的异常处程序对堆栈加以调整,使得在CPU开始执行iret指令时堆栈顶部是返回地址)

      进入内核后,没有进行SAVE_ALL,而是进入相应的异常处理函数(这个函数是包装后的,真正的处理函数在后面)(在此函数里会把真正的处理函数的地址push到栈上),然后jmp到各种异常处理所共用的程序入口error_code,它会像SAVE_ALL那样保存相应的寄存器(没有保存ES),此时内核空间上的内容为:

      | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | 出错代码 error_code | 相应异常处理函数入口 | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX

      (注:如果没有出错代码,则此值为0)

      最后结束时与中断类似(RESTORE_ALL)。

      3) 发生系统调用时,CPU自动保存在内核栈的内容为:

      | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP

      为了与中断和异常的栈一致,在进入系统调用入口(ENTRY(system_call))后会首先push %eax,然后进行SAVE_ALL,此时内核栈上的内容为

      | 用户堆栈的SS | 用户堆栈的ESP | EFLAGS | 用户空间的CS | EIP | EAX | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX

      最后结束时与中断类似(RESTORE_ALL)。

      //-----中断,异常,系统调用 : 结束    


    相关文章推荐:
  • linux僵尸(zombie)进程介绍及清除
  • 高手请教!linux怎样通过pid获取进程信息,如:进程名、进程状态等?
  • linux下进程占用内存空间详解
  • linux命令如何实现重启父进程而不会使其子进程退出
  • linux下进程间通信:共享内存原理及具体用法举例(基于c/c++语言)
  • linux c 编写实现关闭一个进程等一分钟之后重新启动这个进程
  • Linux中最多同时可以开多少个进程,一个进程可以开多少个线程?
  • Linux守护进程 的子进程 终端处理
  • 关于Linux进程0到进程1切换的过程中相关问题的咨询
  • Linux内核进程与应用进程的通信方式有哪些?
  • 在linux,如何用共享内存来实现进程间的通讯?(这些进程没有父子关系)
  • linux下system函数调用shell命令后,怎样让主进程不等子进程返回,接着执行(在线)?
  • linux进程(线程)运行过程中如何获取本进程当前的内存使用状况,统计信息?
  • 如何linux下监控进程及其子进程占用资源?
  • linux下的进程如何访问另外一个进程建立的mutex?
  • linux中用killall命令杀死进程的时候会释放掉该进程所占有的内存吗?
  • linux内核高手进!关于调用__fork()时0号进程的子进程才能与父进程共享PID的困惑
  • 请教:在Linux下怎么样检测一个进程是否是僵尸进程??
  • 请问linux进程的哪些信息保存在内核里?哪些信息又保存在进程空间里?谢谢
  • Linux下请教一个父进程杀死子进程的问题.
  • 怎么查看Linux中所运行的进程,并且知道哪些是停止响应的,用什么命令可以杀掉停止响应的进程?


  • 站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3