文章主要是基于应用程序中的read函数调用流程最终驱动成整体实现流程来说明整个系统调用的流程内容。文章内容是以linux系统的系统调用作为详细分析,分析过程也是基于ARM平台的swi完成,库使用glibc2.26,内核版本是linux4.9。系统调用整体流程较长,一个整体的框架图如下
linux系统调用分为3个部分: 调用请求 ,响应请求 ,功能实现。
linux系统调用流程图如下:
系统调用提供给应用程序的 调用请求接口,调用请求中执行了 软中断的指令,应用程序使用调用请求后, 处理器会产生一个中断,中断服务得到执行,中断服务根据 调用号执行特定的功能实现函数。
系统调用是昂贵的,因为这个过程,涉及到用户态到内核态的切换,而这个切换的过程是通过中断实现的,从用户态切换到内核态需要保护现场,从内核态到用户态时需要恢复现场,也就是上下文切换了,所以一次系统调用的开销,还是蛮大的。
系统调用是操作和理解 linux 内核的关键切入点,系统调用只是入口,真正的工作都是内核完成。内核中会有很多机制来完成这些工作,比如内核的驱动模块、内核的中断系统等,这些都是我们做内核开发时会经常设计的模块。在以后的文章中,我还会写一些关于 linux 内核中断系统、linux 内核启动过程分析、linux 内核 select 实现与字符设备驱动程序测试,敬请期待。
之前有一个读的应用程序read-mem.c,具体内容如下:
#include
#include
#include
#include
int main()
{
intfd = 0;
intdst = 0;
/*打开设备文件*/
fd= open("/dev/memdev0",O_RDWR);
/*写入数据*/
read(fd,&dst, sizeof(int));
printf("dstis %d\n",dst);
/*关闭设备*/
close(fd);
return 0;
}
分析代码流程过程中我们可以是trace工具跟踪系统调用,也可以使用objdump来反汇编代码查看代码处理流程。 利用objdump反汇编编译好的应用程序,分析了应用程序程序中调用read时时候,如何一层层的找到驱动中我们自己实现的对应的read方法。
一、反汇编编译好的应用程序
然后我们将其编译,在编译时候加上-g选项以及静态链接库。
#arm-linux-gcc –static –gread-mem.c –o read-mem
之后再对其进行反汇编,将结果输出到叫dump文件里看具体做了哪些事:
#arm-linux-objdump –D –S read-mem>dump
二、阅读dump文件
因为整个反汇编文件还是挺多的,我们只需要找到main函数里面的read就可以了,在第119行:
/*写入数据*/
read(fd, &dst, sizeof(int));
8258: e24b300c sub r3,fp, #12 ; 0xc
825c: e51b0008 ldr r0,[fp, #-8]
8260: e1a01003 mov r1,r3
8264: e3a02004 mov r2,#4 ; 0x4
8268: eb0028e4 bl 12600<__libc_read>
这里汇编一行一行的去理解比较费时费劲,自己尝试了下,首先就要了解fp寄存器已经程序的堆栈问题,这里前面几行代码就是把传进来的三个参数做一个保存,最后一句是终点,跳到__libc_read里面去了,我们切过去看下,__libc_read,在第10888行,真是长啊,还在文件的上半部分。。。看来静态链接确实会大大的增加文件的大小。
由于__lib_read也比较长,前面部分主要是对当前状态的一个“保护”,r1和r7寄存器的一个堆栈操作,防止调用svc之后,回不来了,但是主要关注第10904开始这两行:
1263c: e3a07003 mov r7,#3 ; 0x3
12640: ef000000 svc 0x00000000
第一行代码,自己在之前系统调用那个帖子里已经分析过了,其实是把系统调用号3传给了arm的r7寄存器,然后调用svc这个指令,svc是一个系统调用指令,执行之后我们的pc指针就会从用户态,切到内核态来执行,到内核态的这个入口是固定的。到了内核态之后,内核就回去取r7当中的这个系统调用号。这里开始就是上一次我们分析的系统调用了,具体步骤再说一遍。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct class *sixthdrv_class;
static struct class_device *sixthdrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,sixth_drv_read将它清0 */
static volatile int ev_press = 0;
static struct fasync_struct *button_async;
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
//static atomic_t canopen = ATOMIC_INIT(1); //定义原子变量并初始化为1
static DECLARE_MUTEX(button_lock); //定义互斥锁
/*
* 确定按键值
*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
kill_fasync (&button_async, SIGIO, POLL_IN);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int sixth_drv_open(struct inode *inode, struct file *file)
{
#if 0
if (!atomic_dec_and_test(&canopen))
{
atomic_inc(&canopen);
return -EBUSY;
}
#endif
if (file->f_flags & O_NONBLOCK)
{
if (down_trylock(&button_lock))
return -EBUSY;
}
else
{
/* 获取信号量 */
down(&button_lock);
}
/* 配置GPF0,2为输入引脚 */
/* 配置GPG3,11为输入引脚 */
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
if (file->f_flags & O_NONBLOCK)
{
if (!ev_press)
return -EAGAIN;
}
else
{
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
}
/* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return 1;
}
int sixth_drv_close(struct inode *inode, struct file *file)
{
//atomic_inc(&canopen);
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
up(&button_lock);
return 0;
}
static unsigned sixth_drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // 不会立即休眠
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static int sixth_drv_fasync (int fd, struct file *filp, int on)
{
printk("driver: sixth_drv_fasync\n");
return fasync_helper (fd, filp, on, &button_async);
}
static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = sixth_drv_open,
.read = sixth_drv_read,
.release = sixth_drv_close,
.poll = sixth_drv_poll,
.fasync = sixth_drv_fasync,
};
int major;
static int sixth_drv_init(void)
{
major = register_chrdev(0, "sixth_drv", &sencod_drv_fops);
sixthdrv_class = class_create(THIS_MODULE, "sixth_drv");
sixthdrv_class_dev = class_device_create(sixthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
return 0;
}
static void sixth_drv_exit(void)
{
unregister_chrdev(major, "sixth_drv");
class_device_unregister(sixthdrv_class_dev);
class_destroy(sixthdrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
module_init(sixth_drv_init);
module_exit(sixth_drv_exit);
MODULE_LICENSE("GPL");
应用程序通过 #include
/* Read NBYTES into BUF from FD. Return the number read or -1. */
ssize_t
__libc_read (int fd, void *buf, size_t nbytes)
{
return SYSCALL_CANCEL (read, fd, buf, nbytes);
}
libc_hidden_def (__libc_read)
libc_hidden_def (__read)
weak_alias (__libc_read, __read)
libc_hidden_def (read)
weak_alias (__libc_read, read)
整个函数的实现过程时比较简单的,主要是通过weak_alias (__libc_read, read)从新定义了read函数,函数中也给出了从新定义函数 __libc_read 实现。之前在反汇编中可以看到整个调用过程,会发现 read 就是直接通过 __libc_read 函数替换掉了。
SYSCALL_CANCEL -> … -> syscall 这个过程需要结合不同的平台去完成,不同的平台调用关系是不同的,对于ARM单板需要结合glibc-2.26/sysdeps/unix/sysdep.h文件和glibc-2.26/sysdeps/unix/sysv/linux/arm/sysdep.h 两个文件进行分析。在
sysdep.h文件中给出了SYSCALL_CANCEL定义
#define SYSCALL_CANCEL(...) \
({ \
long int sc_ret; \
if (SINGLE_THREAD_P) \
sc_ret = INLINE_SYSCALL_CALL (__VA_ARGS__); \
else \
{ \
int sc_cancel_oldtype = LIBC_CANCEL_ASYNC (); \
sc_ret = INLINE_SYSCALL_CALL (__VA_ARGS__); \
LIBC_CANCEL_RESET (sc_cancel_oldtype); \
} \
sc_ret; \
})
#define INLINE_SYSCALL_CALL(...) \
__INLINE_SYSCALL_DISP (__INLINE_SYSCALL, __VA_ARGS__)
在之后的系统调用中具体系统调用都是通过__INLINE_SYSCALL中转,ARM这个中转sysdep.h文件中实现
#undef INLINE_SYSCALL
#define INLINE_SYSCALL(name, nr, args...) \
({ unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args); \
if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0)) \
{ \
__set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, )); \
_sys_result = (unsigned int) -1; \
} \
(int) _sys_result; })
INLINE_SYSCALL 宏一共需要三个参数,分别是:系统调用名称、参数个数、系统调用需要的参数(变长)。INLINE_SYSCALL 支持使用直接使用 SYS_ify 对 name 字段修改了一下,就直接调用 INTERNAL_SYSCALL_RAW。 其中最关键的宏定义时INTERNAL_SYSCALL,具体如下:
#undef INTERNAL_SYSCALL
#define INTERNAL_SYSCALL(name, err, nr, args...) \
INTERNAL_SYSCALL_RAW(SYS_ify(name), err, nr, args)
SYS_ify 只是在 name 前面加上了__NR_,因此这里为__NR_clone。
/* For Linux we can use the system call table in the header file
/usr/include/asm/unistd.h
of the kernel. But these symbols do not follow the SYS_* syntax
so we have to redefine the `SYS_ify' macro here. */
#undef SYS_ify
#define SYS_ify(syscall_name) (__NR_##syscall_name)
__NR_clone 是什么意思呢?在 linux/arch/arm/include/uapi/asm/unistd.h 中,可以看到,__NR_clone 就是 clone 系统调用对应的编号,内核空间不是以字符串来区分系统调用,而是为每个系统调用分配了一个独一无二的编号。
INTERNAL_SYSCALL_RAW 就是我们要看的重点,它封装了进行系统调用的细节。
#else /* ARM */ ARM还有thumb模式,这里说明ARM模式
# undef INTERNAL_SYSCALL_RAW
# define INTERNAL_SYSCALL_RAW(name, err, nr, args...) \
({ \
register int _a1 asm ("r0"), _nr asm ("r7"); \
LOAD_ARGS_##nr (args) \
_nr = name; \
asm volatile ("swi 0x0 @ syscall " #name \
: "=r" (_a1) \
: "r" (_nr) ASM_ARGS_##nr \
: "memory"); \
_a1; })
#endif
INTERNAL_SYSCALL_RAW 中使用了内联汇编的方法产生系统调用,具体为:
把调用参数(args...)保存到 r0-r6 的寄存器中
把系统调用编号保存在 r7 寄存器中
执行 swi 0x0 软中断指令,陷入 linux 内核中
其中,LOAD_ARGS_##nr 和 ASM_ARGS_##nr 分别根据参数个数不同,具有如下系列的宏定义:
#define LOAD_ARGS_0()
#define ASM_ARGS_0
#define LOAD_ARGS_1(a1) \
int _a1tmp = (int) (a1); \
LOAD_ARGS_0 () \
_a1 = _a1tmp;
#define ASM_ARGS_1 ASM_ARGS_0, "r" (_a1)
#define LOAD_ARGS_2(a1, a2) \
int _a2tmp = (int) (a2); \
LOAD_ARGS_1 (a1) \
register int _a2 asm ("a2") = _a2tmp;
#define ASM_ARGS_2 ASM_ARGS_1, "r" (_a2)
#define LOAD_ARGS_3(a1, a2, a3) \
int _a3tmp = (int) (a3); \
LOAD_ARGS_2 (a1, a2) \
register int _a3 asm ("a3") = _a3tmp;
#define ASM_ARGS_3 ASM_ARGS_2, "r" (_a3)
#define LOAD_ARGS_4(a1, a2, a3, a4) \
int _a4tmp = (int) (a4); \
LOAD_ARGS_3 (a1, a2, a3) \
register int _a4 asm ("a4") = _a4tmp;
#define ASM_ARGS_4 ASM_ARGS_3, "r" (_a4)
#define LOAD_ARGS_5(a1, a2, a3, a4, a5) \
int _v1tmp = (int) (a5); \
LOAD_ARGS_4 (a1, a2, a3, a4) \
register int _v1 asm ("v1") = _v1tmp;
#define ASM_ARGS_5 ASM_ARGS_4, "r" (_v1)
#define LOAD_ARGS_6(a1, a2, a3, a4, a5, a6) \
int _v2tmp = (int) (a6); \
LOAD_ARGS_5 (a1, a2, a3, a4, a5) \
register int _v2 asm ("v2") = _v2tmp;
#define ASM_ARGS_6 ASM_ARGS_5, "r" (_v2)
#ifndef __thumb__
# define LOAD_ARGS_7(a1, a2, a3, a4, a5, a6, a7) \
int _v3tmp = (int) (a7); \
LOAD_ARGS_6 (a1, a2, a3, a4, a5, a6) \
register int _v3 asm ("v3") = _v3tmp;
# define ASM_ARGS_7 ASM_ARGS_6, "r" (_v3)
#endif
关于参数传递与寄存器的对应关系,可以参考如下对应关系(读者可以思考当参数大于 7 个时如何处理):
/* Linux takes system call args in registers:
arg 1 r0
arg 2 r1
arg 3 r2
arg 4 r3
arg 5 r4 (this is different from the APCS convention)
arg 6 r5
arg 7 r6
The compiler is going to form a call by coming here, through PSEUDO, with
arguments
syscall number in the DO_CALL macro
arg 1 r0
arg 2 r1
arg 3 r2
arg 4 r3
arg 5 [sp]
arg 6 [sp+4]
arg 7 [sp+8]
We need to shuffle values between R4..R6 and the stack so that the
caller's v1..v3 and stack frame are not corrupted, and the kernel
sees the right arguments.
*/
同时,ARM 对系统调用编号的传递模式也稍有不同,见如下英文:
The ARM EABI user interface passes the syscall number in r7, instead
of in the swi. This is more efficient, because the kernel does not need
to fetch the swi from memory to find out the number; which can be painful
with separate I-cache and D-cache. Make sure to use 0 for the SWI
argument; otherwise the (optional) compatibility code for APCS binaries
may be invoked.
上面这段代码的意思大致是,ARM EABI 用户接口使用 r7 传递系统调用编号,而传统的手段是把系统调用编号作为 swi 的立即数进行传递(SWI{cond} immed_24),这样在陷入内核状态后,需要先拿到 swi 指令的地址,然后在拿到 swi 指令后面的立即数才能拿到系统调用编号,而如果采用 r7 寄存器传递系统调用编号,效率将大大提升。
至此,glibc 的工作就完成了,控制权已经从用户空间陷入内核空间了,那么下面就看一下 linux 内核空间的主要工作。
上文中 swi 指令已经让代码陷入内核空间,swi 是一条软中断指令,那么它对应的中断处理程序时什么呢?先看一下内核中的中断向量表:
.section .vectors, "ax", %progbits
.L__vectors_start:
W(b) swi
W(b) vector_und // 未定义
W(ldr) pc, .L__vectors_start + 0x1000 // swi中断向量
W(b) vector_pabt // 指令预取异常中断
W(b) vector_dabt // 数据中止
W(b) vector_addrexcptn // 地址异常
W(b) vector_irq // IRQ(一般中断)
W(b) vector_fiq // FIQ(快速中断)
可以看到软中断处理函数位于基于 L__vectors_start+0x1000 偏移出(相对于 pc),那么在__vectors_start+0x1000 偏移处有什么呢?0x1000 又有何来源?我们可以从 /arch/arm/kernel/vmlinux.lds.S 链接脚本中找到答案,.vectors 端放在 0xffff0000 起始位置,.stubs 端放在 0xffff0000 + 0x1000 起始位置(其中.vectors 存放了中断向量表,.stubs 存放了对应的中断处理程序)
SECTIONS
{
. = PAGE_OFFSET + TEXT_OFFSET;
.head.text : {
_text = .;
HEAD_TEXT
}
.text : { /* Real text segment */
}
/*
* The vectors and stubs are relocatable code, and the
* only thing that matters is their relative offsets
*/
__vectors_start = .;
.vectors 0xffff0000 : AT(__vectors_start) {
*(.vectors)
}
. = __vectors_start + SIZEOF(.vectors);
__vectors_end = .;
__stubs_start = .;
.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {
*(.stubs)
}
. = __stubs_start + SIZEOF(.stubs);
__stubs_end = .;
}
.vectors 段的内容上文已经看到了,下面来看一下.stubs 段的内容:
.section .stubs, "ax", %progbits
@ This must be the first word 注意这里
.word vector_swi
vector_rst:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
b vector_und
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
可以清晰看到,.stubs 段的起始位置存放的就是 swi 的中断处理程序入口地址(vector_swi),下面是其代码:
/*=============================================================================
* SWI handler
*-----------------------------------------------------------------------------
*/
.align 5
ENTRY(vector_swi)
#ifdef CONFIG_CPU_V7M
v7m_exception_entry
#else
@这个栈帧大小正好是struct pt_regs的大小. struct pt_regs中保存的是
@线程用户态的寄存器上下文(模式上下文).
sub sp, sp, #PT_REGS_SIZE
@ 将r0-r12寄存器压入内核栈中
stmia sp, {r0 - r12} @ Calling r0 - r12
@ 将用户态的sp、lr压入内核栈中
ARM( add r8, sp, #S_PC )
ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr
THUMB( mov r8, sp )
THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lr
@ sprs_svc中保存的是调用swi指令前的cpsr值,这里将它保存在寄存器r8中
mrs r8, spsr @ called from non-FIQ mode, so ok.
@ lr中的值是swi指令的下一行,也就是系统调用的用户态返回地址。将其压入内核栈中
str lr, [sp, #S_PC] @ Save calling PC
@ 将调用swi指令前的cpsr值压入内核栈中
str r8, [sp, #S_PSR] @ Save CPSR
@ 将r0压入内核栈中
str r0, [sp, #S_OLD_R0] @ Save OLD_R0
#endif
zero_fp
alignment_trap r10, ip, __cr_alignment
@使能中断
enable_irq
ct_user_exit
get_thread_info tsk
/*
* Get the system call number.
*/
#if defined(CONFIG_OABI_COMPAT)
/*
* If we have CONFIG_OABI_COMPAT then we need to look at the swi
* value to determine if it is an EABI or an old ABI call.
*/
#ifdef CONFIG_ARM_THUMB
tst r8, #PSR_T_BIT
movne r10, #0 @ no thumb OABI emulation
USER( ldreq r10, [lr, #-4] ) @ get SWI instruction
#else
USER( ldr r10, [lr, #-4] ) @ get SWI instruction
#endif
ARM_BE8(rev r10, r10) @ little endian instruction
#elif defined(CONFIG_AEABI)
/*
* Pure EABI user space always put syscall number into scno (r7).
*/
#elif defined(CONFIG_ARM_THUMB)
/* Legacy ABI only, possibly thumb mode. */
tst r8, #PSR_T_BIT @ this is SPSR from save_user_regs
addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in
USER( ldreq scno, [lr, #-4] )
#else
/* Legacy ABI only. */
USER( ldr scno, [lr, #-4] ) @ get SWI instruction
#endif
uaccess_disable tbl
adr tbl, sys_call_table @ load syscall table pointer
#if defined(CONFIG_OABI_COMPAT)
/*
* If the swi argument is zero, this is an EABI call and we do nothing.
*
* If this is an old ABI call, get the syscall number into scno and
* get the old ABI syscall table address.
*/
bics r10, r10, #0xff000000
eorne scno, r10, #__NR_OABI_SYSCALL_BASE
ldrne tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
bic scno, scno, #0xff000000 @ mask off SWI op-code
eor scno, scno, #__NR_SYSCALL_BASE @ check OS number
#endif
local_restart:
ldr r10, [tsk, #TI_FLAGS] @ check for syscall tracing
stmdb sp!, {r4, r5} @ push fifth and sixth args
tst r10, #_TIF_SYSCALL_WORK @ are we tracing syscalls?
bne __sys_trace
cmp scno, #NR_syscalls @ check upper syscall limit
@ 这里先设置系统调用执行函数sys_xxx()的返回地址为ret_fast_syscall
@ 这设置的是当前线程lr_svc寄存器,当下次通过__switch_to恢复
@ 当前线程的上下文(cpu_context)时首先调用ret_fast_syscall来恢复其用户态
@ 的线程上下文(struct pt_regs).
badr lr, ret_fast_syscall @ return address
@ 以下就是根据系统调用号调用具体的执行函数。
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
add r1, sp, #S_OFF
2: cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back
bcs arm_syscall
mov why, #0 @ no longer a real syscall
b sys_ni_syscall @ not private func
#if defined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)
/*
* We failed to handle a fault trying to access the page
* containing the swi instruction, but we're not really in a
* position to return -EFAULT. Instead, return back to the
* instruction and re-enter the user fault handling path trying
* to page it in. This will likely result in sending SEGV to the
* current task.
*/
9001:
sub lr, lr, #4
str lr, [sp, #S_PC]
b ret_fast_syscall
#endif
ENDPROC(vector_swi)
上面的代码中,最终会根据系统调用编号去 sys_call_table 中调用相应的函数,sys_call_table 在 /arch/arm/kernel/entry-common.S 中定义:
.type sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"
其中重点是 calls.S,路径为 /arch/arm/kernel/calls.S,这里声明了所有的内核系统调用:
/* 0 */ CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork)
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)
CALL(sys_chdir)
CALL(OBSOLETE(sys_time)) /* used by libc4 */
CALL(sys_mknod)
/* 15 */ CALL(sys_chmod)
CALL(sys_lchown16)
CALL(sys_ni_syscall) /* was sys_break */
CALL(sys_ni_syscall) /* was sys_stat */
CALL(sys_lseek)
/* 20 */ CALL(sys_getpid)
CALL(sys_mount)
CALL(OBSOLETE(sys_oldumount)) /* used by libc4 */
CALL(sys_setuid16)
CALL(sys_getuid16)
/* 25 */ CALL(OBSOLETE(sys_stime))
CALL(sys_ptrace)
CALL(OBSOLETE(sys_alarm)) /* used by libc4 */
CALL(sys_ni_syscall) /* was sys_fstat */
CALL(sys_pause)
/* 30 */ CALL(OBSOLETE(sys_utime)) /* used by libc4 */
CALL(sys_ni_syscall) /* was sys_stty */
CALL(sys_ni_syscall) /* was sys_getty */
CALL(sys_access)
CALL(sys_nice)
/* 35 */ CALL(sys_ni_syscall) /* was sys_ftime */
CALL(sys_sync)
CALL(sys_kill)
CALL(sys_rename)
CALL(sys_mkdir)
/* 40 */ CALL(sys_rmdir)
CALL(sys_dup)
CALL(sys_pipe)
CALL(sys_times)
CALL(sys_ni_syscall) /* was sys_prof */
/* 45 */ CALL(sys_brk)
CALL(sys_setgid16)
CALL(sys_getgid16)
CALL(sys_ni_syscall) /* was sys_signal */
CALL(sys_geteuid16)
/* 50 */ CALL(sys_getegid16)
CALL(sys_acct)
CALL(sys_umount)
CALL(sys_ni_syscall) /* was sys_lock */
CALL(sys_ioctl)
/* 55 */ CALL(sys_fcntl)
CALL(sys_ni_syscall) /* was sys_mpx */
CALL(sys_setpgid)
CALL(sys_ni_syscall) /* was sys_ulimit */
CALL(sys_ni_syscall) /* was sys_olduname */
/* 60 */ CALL(sys_umask)
CALL(sys_chroot)
CALL(sys_ustat)
CALL(sys_dup2)
CALL(sys_getppid)
/* 65 */ CALL(sys_getpgrp)
CALL(sys_setsid)
CALL(sys_sigaction)
CALL(sys_ni_syscall) /* was sys_sgetmask */
CALL(sys_ni_syscall) /* was sys_ssetmask */
/* 70 */ CALL(sys_setreuid16)
CALL(sys_setregid16)
CALL(sys_sigsuspend)
CALL(sys_sigpending)
CALL(sys_sethostname)
/* 75 */ CALL(sys_setrlimit)
CALL(OBSOLETE(sys_old_getrlimit)) /* used by libc4 */
CALL(sys_getrusage)
CALL(sys_gettimeofday)
CALL(sys_settimeofday)
/* 80 */ CALL(sys_getgroups16)
CALL(sys_setgroups16)
CALL(OBSOLETE(sys_old_select)) /* used by libc4 */
CALL(sys_symlink)
CALL(sys_ni_syscall) /* was sys_lstat */
/* 85 */ CALL(sys_readlink)
CALL(sys_uselib)
CALL(sys_swapon)
CALL(sys_reboot)
CALL(OBSOLETE(sys_old_readdir)) /* used by libc4 */
/* 90 */ CALL(OBSOLETE(sys_old_mmap)) /* used by libc4 */
CALL(sys_munmap)
CALL(sys_truncate)
CALL(sys_ftruncate)
CALL(sys_fchmod)
/* 95 */ CALL(sys_fchown16)
CALL(sys_getpriority)
CALL(sys_setpriority)
CALL(sys_ni_syscall) /* was sys_profil */
CALL(sys_statfs)
/* 100 */ CALL(sys_fstatfs)
CALL(sys_ni_syscall) /* sys_ioperm */
CALL(OBSOLETE(ABI(sys_socketcall, sys_oabi_socketcall)))
CALL(sys_syslog)
CALL(sys_setitimer)
/* 105 */ CALL(sys_getitimer)
CALL(sys_newstat)
CALL(sys_newlstat)
CALL(sys_newfstat)
CALL(sys_ni_syscall) /* was sys_uname */
/* 110 */ CALL(sys_ni_syscall) /* was sys_iopl */
CALL(sys_vhangup)
CALL(sys_ni_syscall)
CALL(OBSOLETE(sys_syscall)) /* call a syscall */
CALL(sys_wait4)
/* 115 */ CALL(sys_swapoff)
CALL(sys_sysinfo)
CALL(OBSOLETE(ABI(sys_ipc, sys_oabi_ipc)))
CALL(sys_fsync)
CALL(sys_sigreturn_wrapper)
/* 120 */ CALL(sys_clone)
CALL(sys_setdomainname)
CALL(sys_newuname)
CALL(sys_ni_syscall) /* modify_ldt */
CALL(sys_adjtimex)
/* 125 */ CALL(sys_mprotect)
CALL(sys_sigprocmask)
CALL(sys_ni_syscall) /* was sys_create_module */
CALL(sys_init_module)
CALL(sys_delete_module)
/* 130 */ CALL(sys_ni_syscall) /* was sys_get_kernel_syms */
CALL(sys_quotactl)
CALL(sys_getpgid)
CALL(sys_fchdir)
CALL(sys_bdflush)
/* 135 */ CALL(sys_sysfs)
CALL(sys_personality)
CALL(sys_ni_syscall) /* reserved for afs_syscall */
CALL(sys_setfsuid16)
CALL(sys_setfsgid16)
/* 140 */ CALL(sys_llseek)
CALL(sys_getdents)
CALL(sys_select)
CALL(sys_flock)
CALL(sys_msync)
/* 145 */ CALL(sys_readv)
CALL(sys_writev)
CALL(sys_getsid)
CALL(sys_fdatasync)
CALL(sys_sysctl)
/* 150 */ CALL(sys_mlock)
CALL(sys_munlock)
CALL(sys_mlockall)
CALL(sys_munlockall)
CALL(sys_sched_setparam)
/* 155 */ CALL(sys_sched_getparam)
CALL(sys_sched_setscheduler)
CALL(sys_sched_getscheduler)
CALL(sys_sched_yield)
CALL(sys_sched_get_priority_max)
/* 160 */ CALL(sys_sched_get_priority_min)
CALL(sys_sched_rr_get_interval)
CALL(sys_nanosleep)
CALL(sys_mremap)
CALL(sys_setresuid16)
/* 165 */ CALL(sys_getresuid16)
CALL(sys_ni_syscall) /* vm86 */
CALL(sys_ni_syscall) /* was sys_query_module */
CALL(sys_poll)
CALL(sys_ni_syscall) /* was nfsservctl */
/* 170 */ CALL(sys_setresgid16)
CALL(sys_getresgid16)
CALL(sys_prctl)
CALL(sys_rt_sigreturn_wrapper)
CALL(sys_rt_sigaction)
/* 175 */ CALL(sys_rt_sigprocmask)
CALL(sys_rt_sigpending)
CALL(sys_rt_sigtimedwait)
CALL(sys_rt_sigqueueinfo)
CALL(sys_rt_sigsuspend)
/* 180 */ CALL(ABI(sys_pread64, sys_oabi_pread64))
CALL(ABI(sys_pwrite64, sys_oabi_pwrite64))
CALL(sys_chown16)
CALL(sys_getcwd)
CALL(sys_capget)
/* 185 */ CALL(sys_capset)
CALL(sys_sigaltstack)
CALL(sys_sendfile)
CALL(sys_ni_syscall) /* getpmsg */
CALL(sys_ni_syscall) /* putpmsg */
/* 190 */ CALL(sys_vfork)
CALL(sys_getrlimit)
CALL(sys_mmap2)
CALL(ABI(sys_truncate64, sys_oabi_truncate64))
CALL(ABI(sys_ftruncate64, sys_oabi_ftruncate64))
/* 195 */ CALL(ABI(sys_stat64, sys_oabi_stat64))
CALL(ABI(sys_lstat64, sys_oabi_lstat64))
CALL(ABI(sys_fstat64, sys_oabi_fstat64))
CALL(sys_lchown)
CALL(sys_getuid)
/* 200 */ CALL(sys_getgid)
CALL(sys_geteuid)
CALL(sys_getegid)
CALL(sys_setreuid)
CALL(sys_setregid)
/* 205 */ CALL(sys_getgroups)
CALL(sys_setgroups)
CALL(sys_fchown)
CALL(sys_setresuid)
CALL(sys_getresuid)
/* 210 */ CALL(sys_setresgid)
CALL(sys_getresgid)
CALL(sys_chown)
CALL(sys_setuid)
CALL(sys_setgid)
/* 215 */ CALL(sys_setfsuid)
CALL(sys_setfsgid)
CALL(sys_getdents64)
CALL(sys_pivot_root)
CALL(sys_mincore)
/* 220 */ CALL(sys_madvise)
CALL(ABI(sys_fcntl64, sys_oabi_fcntl64))
CALL(sys_ni_syscall) /* TUX */
CALL(sys_ni_syscall)
CALL(sys_gettid)
/* 225 */ CALL(ABI(sys_readahead, sys_oabi_readahead))
CALL(sys_setxattr)
CALL(sys_lsetxattr)
CALL(sys_fsetxattr)
CALL(sys_getxattr)
/* 230 */ CALL(sys_lgetxattr)
CALL(sys_fgetxattr)
CALL(sys_listxattr)
CALL(sys_llistxattr)
CALL(sys_flistxattr)
/* 235 */ CALL(sys_removexattr)
CALL(sys_lremovexattr)
CALL(sys_fremovexattr)
CALL(sys_tkill)
CALL(sys_sendfile64)
/* 240 */ CALL(sys_futex)
CALL(sys_sched_setaffinity)
CALL(sys_sched_getaffinity)
CALL(sys_io_setup)
CALL(sys_io_destroy)
/* 245 */ CALL(sys_io_getevents)
CALL(sys_io_submit)
CALL(sys_io_cancel)
CALL(sys_exit_group)
CALL(sys_lookup_dcookie)
/* 250 */ CALL(sys_epoll_create)
CALL(ABI(sys_epoll_ctl, sys_oabi_epoll_ctl))
CALL(ABI(sys_epoll_wait, sys_oabi_epoll_wait))
CALL(sys_remap_file_pages)
CALL(sys_ni_syscall) /* sys_set_thread_area */
/* 255 */ CALL(sys_ni_syscall) /* sys_get_thread_area */
CALL(sys_set_tid_address)
CALL(sys_timer_create)
CALL(sys_timer_settime)
CALL(sys_timer_gettime)
/* 260 */ CALL(sys_timer_getoverrun)
CALL(sys_timer_delete)
CALL(sys_clock_settime)
CALL(sys_clock_gettime)
CALL(sys_clock_getres)
/* 265 */ CALL(sys_clock_nanosleep)
CALL(sys_statfs64_wrapper)
CALL(sys_fstatfs64_wrapper)
CALL(sys_tgkill)
CALL(sys_utimes)
/* 270 */ CALL(sys_arm_fadvise64_64)
CALL(sys_pciconfig_iobase)
CALL(sys_pciconfig_read)
CALL(sys_pciconfig_write)
CALL(sys_mq_open)
/* 275 */ CALL(sys_mq_unlink)
CALL(sys_mq_timedsend)
CALL(sys_mq_timedreceive)
CALL(sys_mq_notify)
CALL(sys_mq_getsetattr)
/* 280 */ CALL(sys_waitid)
CALL(sys_socket)
CALL(ABI(sys_bind, sys_oabi_bind))
CALL(ABI(sys_connect, sys_oabi_connect))
CALL(sys_listen)
/* 285 */ CALL(sys_accept)
CALL(sys_getsockname)
CALL(sys_getpeername)
CALL(sys_socketpair)
CALL(sys_send)
/* 290 */ CALL(ABI(sys_sendto, sys_oabi_sendto))
CALL(sys_recv)
CALL(sys_recvfrom)
CALL(sys_shutdown)
CALL(sys_setsockopt)
/* 295 */ CALL(sys_getsockopt)
CALL(ABI(sys_sendmsg, sys_oabi_sendmsg))
CALL(sys_recvmsg)
CALL(ABI(sys_semop, sys_oabi_semop))
CALL(sys_semget)
/* 300 */ CALL(sys_semctl)
CALL(sys_msgsnd)
CALL(sys_msgrcv)
CALL(sys_msgget)
CALL(sys_msgctl)
/* 305 */ CALL(sys_shmat)
CALL(sys_shmdt)
CALL(sys_shmget)
CALL(sys_shmctl)
CALL(sys_add_key)
/* 310 */ CALL(sys_request_key)
CALL(sys_keyctl)
CALL(ABI(sys_semtimedop, sys_oabi_semtimedop))
/* vserver */ CALL(sys_ni_syscall)
CALL(sys_ioprio_set)
/* 315 */ CALL(sys_ioprio_get)
CALL(sys_inotify_init)
CALL(sys_inotify_add_watch)
CALL(sys_inotify_rm_watch)
CALL(sys_mbind)
/* 320 */ CALL(sys_get_mempolicy)
CALL(sys_set_mempolicy)
CALL(sys_openat)
CALL(sys_mkdirat)
CALL(sys_mknodat)
/* 325 */ CALL(sys_fchownat)
CALL(sys_futimesat)
CALL(ABI(sys_fstatat64, sys_oabi_fstatat64))
CALL(sys_unlinkat)
CALL(sys_renameat)
/* 330 */ CALL(sys_linkat)
CALL(sys_symlinkat)
CALL(sys_readlinkat)
CALL(sys_fchmodat)
CALL(sys_faccessat)
/* 335 */ CALL(sys_pselect6)
CALL(sys_ppoll)
CALL(sys_unshare)
CALL(sys_set_robust_list)
CALL(sys_get_robust_list)
/* 340 */ CALL(sys_splice)
CALL(sys_sync_file_range2)
CALL(sys_tee)
CALL(sys_vmsplice)
CALL(sys_move_pages)
/* 345 */ CALL(sys_getcpu)
CALL(sys_epoll_pwait)
CALL(sys_kexec_load)
CALL(sys_utimensat)
CALL(sys_signalfd)
/* 350 */ CALL(sys_timerfd_create)
CALL(sys_eventfd)
CALL(sys_fallocate)
CALL(sys_timerfd_settime)
CALL(sys_timerfd_gettime)
/* 355 */ CALL(sys_signalfd4)
CALL(sys_eventfd2)
CALL(sys_epoll_create1)
CALL(sys_dup3)
CALL(sys_pipe2)
/* 360 */ CALL(sys_inotify_init1)
CALL(sys_preadv)
CALL(sys_pwritev)
CALL(sys_rt_tgsigqueueinfo)
CALL(sys_perf_event_open)
/* 365 */ CALL(sys_recvmmsg)
CALL(sys_accept4)
CALL(sys_fanotify_init)
CALL(sys_fanotify_mark)
CALL(sys_prlimit64)
/* 370 */ CALL(sys_name_to_handle_at)
CALL(sys_open_by_handle_at)
CALL(sys_clock_adjtime)
CALL(sys_syncfs)
CALL(sys_sendmmsg)
/* 375 */ CALL(sys_setns)
CALL(sys_process_vm_readv)
CALL(sys_process_vm_writev)
CALL(sys_kcmp)
CALL(sys_finit_module)
/* 380 */ CALL(sys_sched_setattr)
CALL(sys_sched_getattr)
CALL(sys_renameat2)
CALL(sys_seccomp)
CALL(sys_getrandom)
/* 385 */ CALL(sys_memfd_create)
CALL(sys_bpf)
CALL(sys_execveat)
CALL(sys_userfaultfd)
CALL(sys_membarrier)
/* 390 */ CALL(sys_mlock2)
CALL(sys_copy_file_range)
CALL(sys_preadv2)
CALL(sys_pwritev2)
#ifndef syscalls_counted
.equ syscalls_padding, ((NR_syscalls + 3) & ~3) - NR_syscalls
#define syscalls_counted
#endif
.rept syscalls_padding
CALL(sys_ni_syscall)
.endr
其中,CALL 是一个宏,位于 /arch/arm/kernel/entry-common.S,主要用来将每一个系统调用的函数地址依次放在 sys_call_table 表中:
.equ NR_syscalls,0
#define CALL(x) .equ NR_syscalls,NR_syscalls+1
#include "calls.S"
/*
* Ensure that the system call table is equal to __NR_syscalls,
* which is the value the rest of the system sees
*/
.ifne NR_syscalls - __NR_syscalls
.error "__NR_syscalls is not equal to the size of the syscall table"
.endif
#undef CALL
#define CALL(x) .long x
那么具体的系统调用函数的定义在哪里呢?以本文的 fork 系统调用为例,它就位于 /kernel/fork.c 中:
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos = file_pos_read(f.file);
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
可以看到它有几个重载版本,着重关注 SYSCALL_DEFINEx(x 为参数个数),它只是一个简单的宏,方便把其中的参数转换为系统调用的声明格式:
#define SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
asmlinkage long sys_##sname(void)
#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__)
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(SyS##name)))); \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
比如:
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
进行宏展开之后:
asmlinkage long sys_read(unsigned int, fd, char __user *, buf, size_t, count);
sys_read 就只前面系统调用表中那个定义的系统调用函数,这里就对应起来。这个宏类似于注册接口和完成实现,系统调用下来以后根据系统调用表和传下来的编号走到对应的系统调用注册接口中进行函数操作。
下面声明的结构体代码,来源于linux-3.10.1/include/trace/syscall.h
struct syscall_metadata {
const char *name;
int syscall_nr;
int nb_args;
const char **types;
const char **args;
struct list_head enter_fields;
struct ftrace_event_call *enter_event;
struct ftrace_event_call *exit_event;
};
其中,
name,代表该系统调用的名称
syscall_nr,代表该系统调用的编号
nb_args,代表该系统调用的参数个数
在linux源码中,每一个系统调用是通过宏来定义的,比如
SYSCALL_DEFINE1(epoll_create, int, size) {
if (size <= 0)
return -EINVAL;
return sys_epoll_create1(0);
}
SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode) {
return sys_fchmodat(AT_FDCWD, filename, mode);
}
#define SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
asmlinkage long sys_##sname(void)
#define SYSCALL_METADATA(sname, nb, ...) \
static const char *types_##sname[] = { \
__MAP(nb,__SC_STR_TDECL,__VA_ARGS__) \
}; \
static const char *args_##sname[] = { \
__MAP(nb,__SC_STR_ADECL,__VA_ARGS__) \
}; \
SYSCALL_TRACE_ENTER_EVENT(sname); \
SYSCALL_TRACE_EXIT_EVENT(sname); \
static struct syscall_metadata __used \
__syscall_meta_##sname = { \
.name = "sys"#sname, \
.syscall_nr = -1, /* Filled in at boot */ \
.nb_args = nb, \
.types = nb ? types_##sname : NULL, \
.args = nb ? args_##sname : NULL, \
.enter_event = &event_enter_##sname, \
.exit_event = &event_exit_##sname, \
.enter_fields = LIST_HEAD_INIT(__syscall_meta_##sname.enter_fields), \
}; \
static struct syscall_metadata __used \
__attribute__((section("__syscalls_metadata"))) \
*__p_syscall_meta_##sname = &__syscall_meta_##sname;
其中,系统调用的编号syscall_nr,这里被标注为Filled in at boot。
那么我们可以找一下,每一个系统的编号到底是多少。
在linux-3.10.1/arch/xtensa/include/uapi/asm/unistd.h文件中,
可以看到所有系统调用的编号定义,共分成了以下几个部分:
File Operations
File Map / Shared Memory Operations
Socket Operations
Process Operations
File System
System
Signal Handling
Message
IO
Timer
System
Relative File Operations
我们看一下Socket操作的一些系统调用的情况
#ifndef __SYSCALL
# define __SYSCALL(nr,func,nargs)
#endif
#define __NR_socket 96
__SYSCALL( 96, sys_socket, 3)
#define __NR_setsockopt 97
__SYSCALL( 97, sys_setsockopt, 5)
#define __NR_getsockopt 98
__SYSCALL( 98, sys_getsockopt, 5)
#define __NR_shutdown 99
__SYSCALL( 99, sys_shutdown, 2)
#define __NR_bind 100
__SYSCALL(100, sys_bind, 3)
#define __NR_connect 101
__SYSCALL(101, sys_connect, 3)
#define __NR_listen 102
__SYSCALL(102, sys_listen, 2)
#define __NR_accept 103
__SYSCALL(103, sys_accept, 3)
其中,__NR_socket,代表socket()系统调用的编号,被定义为96
__NR_bind,代表bind()系统调用的编号,被定义为100
但实际上,系统调用的编号到底是什么,是根据体系结构来确定的。
不同的体系结构,系统调用的编号不一样,比如x86与x86_64体系结构的系统调用编号就是不一致的。
如何查看自己机器环境的系统调用编号,可以使用ausyscall --dump命令,如下:
[root@wxk ~]# ausyscall --dump
Using x86_64 syscall table:
0 read
1 write
2 open
3 close
4 stat
5 fstat
6 lstat
7 poll
为了组织这些系统调用,操作系统构造了一张例行子程序入口地址表。
所有系统调用的自带参数个数和程序的入口地址,按照系统调用编号,存入系统调用入口地址表中。
比如下表(编号只是举个例子):
编号 自带参数个数 程序入口地址 系统调用名称
0 0 &sys_fork fork
1 2 &sys_open open
2 2 &sys_read read
3 2 &sys_write write
/linux-2.6.11.10/fs/read_write.c
asmlinkage ssize_t 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); //buf为用户态缓冲区,count为读取长度
file_pos_write(file, pos); //将新的偏移地址写回文件
fput_light(file, fput_needed); //释放文件
}
return ret;
}
EXPORT_SYMBOL_GPL(sys_read);
这里名称变掉了,但是我们理解还是sys_read其实在老版本的linux内核里面,确实还是sys_read,但是由于在09年,随着大批量的64位处理器的出现,很多用户在调用的时候,无法填充64位的系统调用,就会被黑客利用,导致系统奔溃和权限升级,所以linux大牛们相处了一套通用的方法,开发出了这一套宏来避免这个bug,这个宏会对参数和系统调用名进行展开解析,从而变成我们需要的sys_read,具体如何做到的,网上有帖子,可以去参看。这里着重关心红色字体ret = vfs_read(file, buf, count, &pos);也就是又调用了一个vfs_read,切过去看。
该函数首先通过fget_light(light表示轻量级的)通过文件描述符,来返回一个文件地址,类型为虚拟文件系统层的struct file,然后获取文件偏移地址,并调用vfs_read即虚拟文件系统的读操作,从这里我们可以看到,无论底层是什么文件系统,由于有VFS这个中间层存在,对文件进行操作都可以把事情交给VFS来处理,这是抽象的好处。
我们可以在sys_read所在的文件里找到vfs_read。
/linux-2.6.11.10/fs/read_write.c
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) {
ret = security_file_permission (file, MAY_READ); //检查是否有读的权限
if (!ret) {
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos); //如有则调用相应文件系统的read函数
else
ret = do_sync_read(file, buf, count, pos); //否则调用这个函数
if (ret > 0) {
dnotify_parent(file->f_dentry, DN_ACCESS); //通知父目录文件已获取
current->rchar += ret;
}
current->syscr++; //一些I/O次数的统计
}
}
return ret;
}
EXPORT_SYMBOL(vfs_read);
首先看函数参数,和我们的read是一致的,然后下面一行红字,判断file结构体里面对应的file_operations里面我们驱动当中的read是否存在,存在的的话就
ret = file->f_op->read(file, buf, count, pos);
调用我们驱动里面实现的read方法,然后一层层的返回过来,给ret,整个这个函数的返回值就用这个ret作为返回值。
我们可以看到,vfs_read函数只是检查了一些状态,就使用回调函数 file->f_op->read,使用相应文件系统的read函数继续进行操作,这个file_operations应该是open file的时候就已经填好的,我们可以/linux-2.6.11.10/fs/ext2/file.c里找到ext2所有的文件操作,如下,其实在新内核里,read和write之类的操作已经改了。
这里需要注意的是不同类型的设备文件读函数实现是不一样的,对于复杂的的驱动设备(存储类设备)曾哥读写过程时非常复杂的。对于一个具体读设备复杂的读逻辑不做复杂研究。我们这里只是针对一个最简单的按键设备驱动进行分析,按键驱动的代码如下。
ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
if (file->f_flags & O_NONBLOCK)
{
if (!ev_press)
return -EAGAIN;
}
else
{
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
}
/* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return 1;
}
当系统调用转到这个部分的时候
六、总结
应用程序read->调用__libc_read->把调用号给r7,产生swi中断,执行svc指令切换到内核->内核读走调用号,与系统调用号表格查找对比找出是哪个系统调用(entry-common.S和calls.S)->调用SYSCALL_DEFINE3及sys_read->调用vfs_read->vfs_read调用与file绑定的file_operations里面的具体的我们实现的驱动的read->返回值给用户空间,结束!
Q1:是否可以同时执行多个swi指令?
如果是单核的情况下硬件线程也是唯一的,同一个时间只有一条指令在ARM内核内运行,这个是这一条指令可以是内核的代码也可以是驱动的代码也可以是应用程序的代码。但是同一个时间只有一条指令在执行。所以单核的情况下,一个应用程序执行了swi指令以后就会进入待内核态运行了。如果这个时候内核被硬件中断或者其他内容给抢占走了,这个swi指令会在抢占内容执行完毕以后再继续执行并将最终的结果返回给用户调用。对于多内核多硬件线程的这种具体不是很了解,不知道硬件是怎么设计的。
Q2:如果是多核,SWI指令不同系统调用是否可以抢占?
Q3:
对于复杂的比方说内存读写的具体参见如下这个博客的分析:
————————————————
版权声明:本文为CSDN博主「xcshuan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xcshuan/article/details/82942010