复习---Linux系统调用

时间:2022-03-18 16:34:43
一、系统调用知识 系统调用,可以参考http://blog.csdn.net/skyflying2012/article/details/10044343 ####Linux内核在系统调用时是通过寄存器而不是通过堆栈传递参数的,显然能够通过寄存器传递的信息量并不大,所以传递的参数大多是指针,这样才能通过指针找到更大的数据块 ####从内核中可以直接访问当前进程的用户空间,所使用的虚拟地址也与当前进程处于用户空间时的地址完全相同,但是,反过来就不可以了 ####文件include/asm-i386./unstd.h上为每个系统调用定义了一个唯一的编号 ----》系统调用号 ####系统调用跳转表sys_call_table[]是一个函数指针数组,跳转时以系统调用号为下标在数组中找到相应的函数指针。(entry.s) ####凡是内核不支持的系统调用号全部都指向sys_ni_syscall(),这个函数只是返回一个出错代码:-ENOSYS,表示该系统调用尚未实现 ####系统调用的参数也是通过寄存器传给内核的,在x86系统上,系统调用的前5个参数放在ebx,ecx,edx,esi和edi中,如果参数多的话,还需要用个单独的寄存器存放指向所有参数在用户空间地址的指针。 ####为Linux添加新的系统调用是件相对容易的事情,主要包括有4个步骤:编写系统调用服务例程;添加系统调用号;修改系统调用表;重新编译内核并测试新添加的系统调用。

Linux用户态到内核态之间的切换示意图(在X86下,系统调用由0x80号中断完成)


复习---Linux系统调用

二、系统调用源码剖析 在Unistd.h (include\asm-i386)文件中,定义了288个系统调用号以及7个_syscall宏 ####_syscall宏(7个) _syscall0(type,name); _syscall1(type,name,type1,arg1); _syscall2(type,name,type1,arg1,type2,arg2); _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3); _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4); _syscall5type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5); _syscall6type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6); 其中,type表示所生成系统调用的返回值类型,name表示该系统调用的名称,typeN、argN分别表示第N个参数的类型和名称,它们的数目和_syscall后面的数字一样大。这些宏的作用是创建名为name的函数,_syscall后面跟的数字指明了该函数的参数的个数。 分析一个最简单的宏(没有任何参数的宏) #define _syscall0(type,name) \ type name(void) \ { \ long __res; \     __asm__ volatile ("int $0x80" \      //80号中断     : "=a" (__res) \          ///例如执行fork(),转到内核态以后执行完的结果会放入eax寄存器中,将eax的值存放在_res中     : "0" (__NR_##name)); \          //例如执行fork(),匹配成_NR##fork宏,替换成fork()的系统调用号存放在eax中 __syscall_return(type,__res); \ } 关于进程创建的三个系统调用: fork() 全部复制,父进程所有的资源全部通过数据结构的复制遗传给子进程 clone() 可以将资源有选择性的复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享 vfork() 创建线程,除PCB和系统空间堆栈以外的资源全都通过数据结构指针的复制遗传 复习---Linux系统调用

系统函数pid_t fork(void); 调用内核中的 asmlinkage int sys_fork(struct pt_regs regs); regs指向通用寄存器的值,是在从用户态切换到内核态时被保存在内核堆栈中的。 asmlinkage int sys_fork(struct pt_regs regs) {   return do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL); } long do_fork(unsigned long clone_flags,   unsigned long stack_start,   struct pt_regs *regs,   unsigned long stack_size,   int __user *parent_tidptr,   int __user *child_tidptr) do_fork()中调用的一个至关重要的函数copy_process函数的分析: 1、调用dup_task_struct()为子进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同。此时子进程和父进程的描述符是完全相同的。 p = dup_task_struct(current)---->(struct task_struct *tsk---------->tsk = alloc_task_struct()从slab层分配了一个关于进程描述符的slab) 2、检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。 3、子进程着手使自己与父进程区别开来,为进程的task_struct、tss做个性化设置,进程描述符内的许多成员都要被清0或设置为初始值。那些不是继承而来的进程描述符成员,主要是统计信息。task_struct中的大多数数据都依然未被修改。 4、为子进程创建第一个页表,将进程0的页表项内容赋给这个页表。 copy_process()————>copy_fs():为子进程复制父进程的页目录项 ,_copy_fs_struct(current->fs)中current指针表示当前进程也就是父进程的 关于fork()系统调用,可以参考http://www.cnblogs.com/qiuheng/p/5752284.html