系统调用是什么???
系统调用是操作系统为用户提供的一系列API;系统调用将用户的请求发给内核,内核执行完以后,将结果返回给用户;
以open为例,进行系统调用:
分析Linux2.6.11版本
<1>通过在unistd.h中的函数名的拼接;
<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中;