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
(2)calls.S
特定平台的calls.S中定义了该平台的系统调用表,arm平台的系统调用表如下所示(部分):
(3)CALL()
上述宏CALL()定义如下:
因此,CALL(sys_exit)可展开为:
3 系统调用入口
系统调用是从什么地方开始的呢?答案是sys_syscall标号处,每当出发系统调用中断时,就自动跳转到该标号处继续执行:
由上源码可见,系统调用入口为sys_syscall标号,r0寄存器保存系统调用号,r8寄存器保存系统调用表,程序根据r0和r8确定系统调用函数(例如sys_read()),然后跳转到该系统调用函数继续执行。
4 系统调用号
上述系统调用表是使用汇编定义的,为了便于使用,使用C语言定义了系统调用号。
特定平台的unstd.h中定义了该平台的系统调用号,arm平台的系统调用号如下所示(部分):
注:不同平台的系统调用号不一定相同,但是必须和系统调用表对应。
5 系统调用函数声明
在syscalls.h头文件中,声明了所有系统调用(平台无关):
6 系统调用函数实现
上述头文件只是声明了系统调用,这里以sys_read()为例重点分析系统调用函数的实现。
试图通过“grep -rnw sys_read”来搜索sys_read()系统调用的实现时,却找不到!
通过分析源码发现,系统调用都通过类似于SYSCALL_DEFINE3()的宏(数字3表示系统调用参数个数为3)来定义,例如:
(1)sys_read()
展开上述宏SYSCALL_DEFINE3()得:
其它宏如下所示:
(2)vfs_read()
通过分析上述sys_read()函数发现,它调用的一个关键函数是vfs()_read():
参考资料
[1]向linux内核添加系统调用新老内核比较
[2]Linux内核中添加新的系统调用
[3]窥探 kernel --- 系统调用过程分析
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寄存器保存系统调用表,程序根据r0和r8确定系统调用函数(例如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 --- 系统调用过程分析
相关文章推荐
- Linux 系统中安装网卡驱动时出现"设备eth0似乎不存在"解决办法
- linux多线程驱动中调用udelay()对系统的影响
- Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)
- Linux网络设备的系统调用
- linux驱动开发-系统调用
- Linux系统下的硬件驱动-USB设备
- linux驱动学习--第八天:第五章 Linux 文件系统与设备文件系统(二) 之 Linux 文件系统
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析
- linux驱动学习--第十天:第五章 Linux 文件系统与设备文件系统(四) 之 设备文件系统 devfs 和 udev
- 嵌入式Linux系统下I2C设备驱动程式的研发
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析
- Linux的USB-Serial驱动(从系统初始化到生成tty设备的全过程)
- Linux的USB-Serial驱动(从系统初始化到生成tty设备的全过程) .
- linux-i2c驱动 之 i2c设备层的注册过程probe函数如何被调用分析
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析
- 嵌入式Linux系统中I2C总线设备的驱动设计
- linux驱动学习--第七天:第五章 Linux 文件系统与设备文件系统 之 linux文件操作
- Linux系统下设备驱动的安全端口分配
- linux驱动开发-经典的系统调用BUG
- Linux设备驱动开发详解-Note(10)--- Linux 文件系统与设备文件系统(2)