一、系统调用知识
系统调用,可以参考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号中断完成)
二、系统调用源码剖析
在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和系统空间堆栈以外的资源全都通过数据结构指针的复制遗传
系统函数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, ®s, 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