系统调用和进程切换时的寄存器信息保存在哪里?

系统调用是什么???
系统调用是操作系统为用户提供的一系列API;系统调用将用户的请求发给内核,内核执行完以后,将结果返回给用户;
以open为例,进行系统调用:
分析Linux2.6.11版本
<1>通过在unistd.h中的函数名的拼接;
系统调用和进程切换时的寄存器信息保存在哪里?_第1张图片

<2>找到对应的系统调用号
系统调用和进程切换时的寄存器信息保存在哪里?_第2张图片
<3>然后将此系统调用号通过eax寄存器告知内核,在执行0x80号中断的时候,eax寄存器中放的是5;
<4>将用户空间的esp等信息压入内核栈,在内核栈上执行系统调用;
<5>系统调用执行完毕,将结果写入eax寄存器中,将用户空间的信息弹出内核栈,执行用户空间的代码;

系统调用时的当前进程要进行模式的切换,要从用户态切换到内核态,那么用户态的一些寄存器信息,就要进行压栈的操作,压入内核栈,以免下次进行现场恢复(恢复到用户空间)的时候,直接从内核栈弹出寄存器的信息,并让eax寄存器带上内核完成系统调用时的结果;

进程切换来了。。。
当我们进程进行切换的时候,用户态的寄存器信息,是保存在哪里的呢?用户态的信息是保存在task_struct里面的成员变量thread里面;

struct task_struct {
    /**
     * 进程状态。
     */
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    /**
     * 进程的基本信息。
     */
    struct thread_info *thread_info;
    atomic_t usage;
    unsigned long flags;    /* per process flags, defined below */
    unsigned long ptrace;

    int lock_depth;     /* Lock depth */

    /**
     * 进行的动态优先权和静态优先权
     */
    int prio, static_prio;
    /**
     * 进程所在运行队列。每个优先级对应一个运行队列。
     */
    struct list_head run_list;
    /**
     * mm:指向内存区描述符的指针
     */
    struct mm_struct *mm, *active_mm;
    struct thread_struct thread;
    ...
    }

针对上面的代码,我们来说一下thread和thread_info的区别;

以下为thread的结构:

/**
 * 进程被切换出去后,内核把它的硬件上下文保存在这个结构中。
 * 它包含大部分CPU寄存器,但是不包含eax、ebx这样的寄存器。
 */
struct thread_struct {
/* cached TLS descriptors. */
    struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES];
    unsigned long   esp0;
    unsigned long   sysenter_cs;
    unsigned long   eip;
    unsigned long   esp;
    unsigned long   fs;
    unsigned long   gs;
/* Hardware debugging registers */
    unsigned long   debugreg[8];  /* %%db0-7 debug registers */
/* fault info */
    unsigned long   cr2, trap_no, error_code;
/* floating point info */
    /**
     * 为支持选择性装入FPU、MMX和XMM寄存器,引入此结构。
     * 当切换进程时,将进程的这些寄存器保存在i387结构中。
     */
    union i387_union    i387;
/* virtual 86 mode info */
    struct vm86_struct __user * vm86_info;
    unsigned long       screen_bitmap;
    unsigned long       v86flags, v86mask, saved_esp0;
    unsigned int        saved_fs, saved_gs;
/* IO permissions */
    unsigned long   *io_bitmap_ptr;
/* max allowed port in the bitmap, in bytes: */
    unsigned long   io_bitmap_max;
};

以上代码可以看出来我们的thread里面保存的是一些进程切换时,寄存器中的内容,当进行进程切换的时候,我们需要保存寄存器的信息,把寄存器的信息赋值给thread里面的各个变量;

thread_info的结构如下:

struct thread_info {
    struct task_struct  *task;      /* main task structure */
    struct exec_domain  *exec_domain;   /* execution domain */
    /**
     * 如果有TIF_NEED_RESCHED标志,则必须调用调度程序。
     */
    unsigned long       flags;      /* low level flags */
    /**
     * 线程标志:
     *     TS_USEDFPU:表示进程在当前执行过程中,是否使用过FPU、MMX和XMM寄存器。
     */
    unsigned long       status;     /* thread-synchronous flags */
    /**
     * 可运行进程所在运行队列的CPU逻辑号。
     */
    __u32           cpu;        /* current CPU */
    __s32           preempt_count; /* 0 => preemptable, <0 => BUG */


    mm_segment_t        addr_limit; /* thread address space:
                           0-0xBFFFFFFF for user-thead
                           0-0xFFFFFFFF for kernel-thread
                        */
    struct restart_block    restart_block;

    unsigned long           previous_esp;   /* ESP of the previous stack in case
                           of nested (IRQ) stacks
                        */
    __u8            supervisor_stack[0];
};

thread_info是保存不同体系下进程的信息,不同的体系结构可能进程需要存储的信息不尽相同, 这就需要我们实现一种通用的方式, 我们将体系结构相关的部分和无关的部门进行分离,这就是thread_info的作用;

是不是会有小伙伴会问那当我一个进程进行系统调用的时候,其实我并不知道,当我的系统调用返回的时候,会不会发生进程切换,我进程系统调用前是不知道的,那么我们为什么进行模式切换时,要把用户的栈信息,压入内核栈呢?我们为什么不直接保存在thread里面,这样的话,当系统调用返回的时候,如果刚好又要进行切换的时候,我们的用户栈信息,已经保存在thread里面了,不就好了么???

其实,对于这个问题,我自己也是很郁闷,通过问苏老师,我终于才懂了;
苏大神说:当进行系统调用的时候,当前进程的时间片就停止了,由内核去完成系统调用,当我们系统调用返回的时候,时间片就可以重新开始计时,无论怎么样,系统调用以后,一定是有时间让内核把信息,返回给用户,系统调用的结果会保存在eax寄存器中,如果系统刚调用完毕,准备返回用户空间,就在此时,发生了进程切换,那么别的进程就会改掉eax的值,那么当我下次进行恢复的时候,还得重新调用系统调用,这样子的话,对于系统的资源就态浪费了;所以说进行系统调用的返回值一定是返回给用户的,当用户吧eax寄存器的值保存起来以后,标志这系统调用完毕,接下来切不切换进程就没有啥影响了;

当发生系统调用或者中断的时候,中断发生前夕,要把所有相关寄存器的内容都保存在内核堆栈中,由宏SAVE_ALL宏完成 :

#define SAVE_ALL \ 
cld; \ 
pushl %es; \ 
pushl %ds; \ 
pushl %eax; \ 
pushl %ebp; \ 
pushl %edi; \ 
pushl %esi; \ 
pushl %edx; \ 
pushl %ecx; \ 
pushl %ebx; \ 
movl $(__KERNEL_DS),%edx; \ 
movl %edx,%ds; \ 
movl %edx,%es;

恢复现场的宏RESTORE_ALL :
从中断返回时,恢复相关寄存器的内容,这是通过RESTORE_ALL宏完成的:


#define RESTORE_ALL \ 
popl %ebx; \ 
popl %ecx; \ 
popl %edx; \ 
popl %esi; \ 
popl %edi; \ 
popl %ebp; \ 
popl %eax; \ 
1: popl %ds; \ 
2: popl %es; \ 
addl $4,%esp; \ 
3: iret;

总结:系统调用的时候,用户态的栈信息压入内核栈;进程切换的时候,用户态的栈信息保存在task_struct中的成员thread中;

你可能感兴趣的:(linux)