您的位置:首页 > 运维架构 > Linux

Linux设备驱动--系统调用

2016-10-24 13:20 190 查看
1 开发环境

    Host:Ubuntu14.04(64bit)

    Target: smdk2410

    Kernel: linux-2.6.39.4

2 系统调用表

    所有系统调用都定义在系统调用表中,当系统调用中断发生时,系统就根据系统调用号在该表中查找需要执行的系统调用函数。这里先说明系统调用表是如何定义的:

(1)sys_call_table

.type   sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"
#undef ABI
#undef OBSOLETE
/* arch/arm/kernel/entry-common.S */


(2)calls.S

    特定平台的calls.S中定义了该平台的系统调用表,arm平台的系统调用表如下所示(部分):

/* 0 */     CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)
CALL(sys_read)
CALL(sys_write)
/* 5 */     CALL(sys_open)
CALL(sys_close)
CALL(sys_ni_syscall)        /* was sys_waitpid */
CALL(sys_creat)
CALL(sys_link)
/* 10 */    CALL(sys_unlink)
CALL(sys_execve_wrapper)
CALL(sys_chdir)
CALL(OBSOLETE(sys_time))    /* used by libc4 */
/* 源文件:arch/arm/kernel/calls.S */

(3)CALL()

    上述宏CALL()定义如下:    

#define CALL(x) .long x
/* 源文件:arch/arm/kernel/entry-common.S */


    因此,CALL(sys_exit)可展开为:

.long sys_exit
    其它的类似。
3 系统调用入口

    系统调用是从什么地方开始的呢?答案是sys_syscall标号处,每当出发系统调用中断时,就自动跳转到该标号处继续执行:

/*============================================================================
* Special system call wrappers
*/
@ r0 = syscall number
@ r8 = syscall table
sys_syscall:
bic scno, r0, #__NR_OABI_SYSCALL_BASE
cmp scno, #__NR_syscall - __NR_SYSCALL_BASE
cmpne   scno, #NR_syscalls  @ check range
stmloia sp, {r5, r6}        @ shuffle args
movlo   r0, r1
movlo   r1, r2
movlo   r2, r3
movlo   r3, r4
ldrlo   pc, [tbl, scno, lsl #2]
b   sys_ni_syscall
ENDPROC(sys_syscall)
/* 源文件:arch/arm/kernel/entry-common.S */


    由上源码可见,系统调用入口为sys_syscall标号,r0寄存器保存系统调用号,r8寄存器保存系统调用表,程序根据r0r8确定系统调用函数(例如sys_read()),然后跳转到该系统调用函数继续执行。

4 系统调用号

    上述系统调用表是使用汇编定义的,为了便于使用,使用C语言定义了系统调用号。

    特定平台的unstd.h中定义了该平台的系统调用号,arm平台的系统调用号如下所示(部分):

/*
* This file contains the system call numbers.
*/
#define __NR_restart_syscall        (__NR_SYSCALL_BASE+  0)
#define __NR_exit           (__NR_SYSCALL_BASE+  1)
#define __NR_fork           (__NR_SYSCALL_BASE+  2)
#define __NR_read           (__NR_SYSCALL_BASE+  3)
#define __NR_write          (__NR_SYSCALL_BASE+  4)
#define __NR_open           (__NR_SYSCALL_BASE+  5)
#define __NR_close          (__NR_SYSCALL_BASE+  6)
/* 7 was sys_waitpid */
#define __NR_creat          (__NR_SYSCALL_BASE+  8)
#define __NR_link           (__NR_SYSCALL_BASE+  9)
#define __NR_unlink         (__NR_SYSCALL_BASE+ 10)
#define __NR_execve         (__NR_SYSCALL_BASE+ 11)
#define __NR_chdir          (__NR_SYSCALL_BASE+ 12)
/* 头文件:arch/arm/include/asm/unistd.h */


:不同平台的系统调用号不一定相同,但是必须和系统调用表对应。

5 系统调用函数声明

    在syscalls.h头文件中,声明了所有系统调用(平台无关):

asmlinkage long sys_time(time_t __user *tloc);
asmlinkage long sys_stime(time_t __user *tptr);
asmlinkage long sys_gettimeofday(struct timeval __user *tv,
struct timezone __user *tz);
asmlinkage long sys_settimeofday(struct timeval __user *tv,
struct timezone __user *tz);
asmlinkage long sys_adjtimex(struct timex __user *txc_p);
asmlinkage long sys_times(struct tms __user *tbuf);
/* 头文件:include/linux/syscalls.h */

6 系统调用函数实现

    上述头文件只是声明了系统调用,这里以sys_read()为例重点分析系统调用函数的实现。

    试图通过“grep -rnw sys_read”来搜索sys_read()系统调用的实现时,却找不到!

    通过分析源码发现,系统调用都通过类似于SYSCALL_DEFINE3()的宏(数字3表示系统调用参数个数为3)来定义,例如:

(1)sys_read()

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;

file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_read(file, buf, count, &pos);
file_pos_write(file, pos);
fput_light(file, fput_needed);
}

return ret;
}
/* 源文件:fs/read_write.c */


    展开上述宏SYSCALL_DEFINE3()得:

asmlinkage long sys_read(unsigned int, fd, char __user *, buf, size_t, count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;

file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_read(file, buf, count, &pos); /* 调用vfs_read()! */
file_pos_write(file, pos);
fput_light(file, fput_needed);
}

return ret;
}
/* 源文件:fs/read_write.c */


    其它宏如下所示:

#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__)
/* 头文件:include/linux/syscalls.h */

(2)vfs_read()

    通过分析上述sys_read()函数发现,它调用的一个关键函数是vfs()_read():

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;

if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
return -EINVAL;
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
return -EFAULT;

ret = rw_verify_area(READ, file, pos, count);
if (ret >= 0) {
count = ret;
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos); /* 调用file->f_op->read()!*/
else
ret = do_sync_read(file, buf, count, pos);
if (ret > 0) {
fsnotify_access(file);
add_rchar(current, ret);
}
inc_syscr(current);
}

return ret;
}

EXPORT_SYMBOL(vfs_read);
/* 源文件:fs/read_write.c */
    分析上述vfs_read()可知,它最终调用了f_op->read()。在编写设备驱动程序时,很重要的一步就是实现f_op->read(),这里就是调用该f_op->read()的地方!

参考资料

[1]向linux内核添加系统调用新老内核比较

[2]Linux内核中添加新的系统调用

[3]窥探 kernel --- 系统调用过程分析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐