系统调用彻底理解

用户程序需要系统提供服务的时候,会通过系统调用产生一个int 0x80的软中断,就会进入到系统调用的入口函数,入口函数存放在以下文件当中:

以下是系统调用的入口:

ENTRY(system_call)

RING0_INT_FRAME # cant unwind into user space anyway

pushl %eax # save orig_eax ,将系统调用号压入栈中

CFI_ADJUST_CFA_OFFSET 4

SAVE_ALL     #将寄存器的值压入堆栈当中,压入堆栈的顺序对应着结构体struct pt_regs ,当出栈的时候,就将这些值传递到结构体struct pt_regs里面的成员,从而实现从汇编代码向C程序传递参数。

Struct pt_regs 对应定义在

struct pt_regs {

long ebx;

long ecx;

long edx;

long esi;

long edi;

long ebp;

long eax;

int  xds;

int  xes;

int  xfs;

int  xgs;

long orig_eax;

long eip;

int  xcs;

long eflags;

long esp;

int  xss;

};

#GET_THREAD_INFO宏获得当前进程的thread_info结构的地址,获取当前进程的信息。

GET_THREAD_INFO(%ebp)

# system call tracing in operation / emulation

#thread_inof结构中flag字段的_TIF_SYSCALL_TRACE_TIF_SYSCALL_AUDIT

#被置1。如果发生被跟踪的情况则转向相应的处理命令处。

testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)

jnz syscall_trace_entry #比较结果不为零的时候跳转。

#对用户态进程传递过来的系统调用号的合法性进行检查。

#如果不合法则跳syscall_badsys标记的命令处。

cmpl $(nr_syscalls), %eax

jae syscall_badsys  #比较结果大于或者等于最大的系统调用号的时候跳转,不合法

#合法则跳转到相应系统调用号所对应的服务例程当中,

#也就是在sys_call_table表中找到了相应的函数入口点。

#由于sys_call_table表的表项占4字节,因此获得服务例程指针的具体方法

#是将由eax保存的系统调用号乘以4再与sys_call_table表的基址相加。

syscall_call:

call *sys_call_table(,%eax,4)

movl %eax,PT_EAX(%esp) # store the return value 将保存的结果返回。

接下来,会进入到系统调用表查找到系统调用服务程序的入口函数的地址,再进行跳转,整个过程如下图所示:

ENTRY(sys_call_table)

.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */

.long sys_exit

.long ptregs_fork

.long sys_read

.long sys_write

.long sys_open /* 5 */

.long sys_close

.long sys_waitpid

.long sys_creat

.long sys_link

.long sys_unlink /* 10 */

.long ptregs_execve

例如我们跟踪一下系统调用open的打开流程:

1、open的系统调用号,存放于

#define __NR_restart_syscall      0

#define __NR_exit   1

#define __NR_fork   2

#define __NR_read   3

#define __NR_write   4

#define __NR_open   5

#define __NR_close   6

#define __NR_waitpid   7

#define __NR_creat   8

#define __NR_link   9

#define __NR_unlink  10

2、系统调用函数的原型在:

asmlinkage long sys_open(const char __user *filename,

int flags, int mode);

其中这里使用了一个宏asmlinkage ,我们再看一下它在系统里的定义:

#ifdef CONFIG_X86_32

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

后面的 __attribute__((regparm(0)))表示的是不通过寄存器来传递参数,通过栈来传递

所以系统调用的入口函数里面

ENTRY(system_call)

SAVE_ALL     #将寄存器的值压入堆栈当中,压入堆栈的顺序对应着结构体struct pt_regs ,当出栈的时候,就将这些值传递到结构体struct pt_regs里面的成员,从而实现从汇编代码向C程序传递参数。

定义了这个SAVE_ALL是将参数压倒堆栈里面,然后通过堆栈来进行参数的传递。

3、经过了系统调用入口函数之后,会调用到

syscall_call:

call *sys_call_table(,%eax,4)

sys_call_table每一项占用4个字节。system_call函数可以读取eax寄存器获得当前系统调用的系统调用号,将其乘以4生成偏移地址,然后以sys_call_table为基址,基址加上偏移地址所指向的内容即是应该执行的系统调用服务例程的地址。

ENTRY(sys_call_table)

.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */

.long sys_exit

.long ptregs_fork

.long sys_read

.long sys_write

.long sys_open /* 5 */

对应于系统调用sys_open是系统调用服务程序的入口地址

5、在新的内核中,函数的实现并不是对应这sys_XXX的一个函数的实现。而是会经过一个宏的封装,在

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)

{

long ret;

if (force_o_largefile())

flags |= O_LARGEFILE;

ret = do_sys_open(AT_FDCWD, filename, flags, mode);

/* avoid REGPARM breakage on x86: */

asmlinkage_protect(3, ret, filename, flags, mode);

return ret;

}

这个宏SYSCALL_DEFINE3的定义在中,我们可以看到有一些这样的宏的定义:

#define SYSCALL_DEFINE0(sname) \

static const struct syscall_metadata __used \

  __attribute__((__aligned__(4))) \

  __attribute__((section("__syscalls_metadata"))) \

  __syscall_meta_##sname = { \

.name  = "sys_"#sname, \

.nb_args  = 0, \

}; \

asmlinkage long sys_##sname(void)

#else

#define SYSCALL_DEFINE0(name)    asmlinkage long sys_##name(void)

#endif

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

结合系统函数sys_open的定义

asmlinkage long sys_open(const char __user *filename,

int flags, int mode);

可以知道,SYSCALL_DEFINE3中的数字3表示的是我们这个函数需要传递3个参数。

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

##的意思就是宏中的字符直接替换,如上面_##name展开之后就会变成_open

SYSCALL_DEFINEx(3, _open, __VA_ARGS__)

#define SYSCALL_DEFINEx(x, sname, ...) \

__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

#define __SYSCALL_DEFINEx(x, name, ...) \

asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \

static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \

asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \

{ \

__SC_TEST##x(__VA_ARGS__); \

return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \

} \

SYSCALL_ALIAS(sys##name, SyS##name); \

static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))

所以上面的宏完整展开之后会变成这样子,所以最后会调用到 系统调用服务程序sys_open这个函数。

#define SYSCALL_DEFINE3(3, _open, ...) \

__SYSCALL_DEFINE3(3, _open, __VA_ARGS__)

#define __SYSCALL_DEFINE3(3, _open, ...) \

asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__)); \

static inline long SYSC_open(__SC_DECL3(__VA_ARGS__)); \

asmlinkage long SyS_open(__SC_LONG3(__VA_ARGS__)) \

{ \

__SC_TEST3(__VA_ARGS__); \

return (long) SYSC_open(__SC_CAST3(__VA_ARGS__)); \

} \

SYSCALL_ALIAS(sys_open, SyS_open); \

static inline long SYSC_open(__SC_DECL3(__VA_ARGS__))

所以当我们自己定义一个不需要传递参数的系统调用的时候,可以这样定义我们的函数:

SYSCALL_DEFINE0(mycall)

{

printk("This is my_sys_call\n");

return 0;

}

你可能感兴趣的:(Linux设备驱动)