系统调用源码剖析
1. lib目录下的系统调用api
- close()
1
2
3
4
#define __LIBRARY__
#include <unistd.h>
_syscall1(int,close,int,fd)
- write()
1
2
3
4
#define __LIBRARY__
#include <unistd.h>
_syscall3(int,write,int,fd,const char *,buf,off_t,count)
2. _syscall 详解
_syscall
是一个宏,在include/unistd.h
中定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define _syscall0(type,name) \
#define _syscall1(type,name,atype,a) \
#define _syscall2(type,name,atype,a,btype,b) \
/*
每个系统调用 2 + 2*n个参数
type, name, atype,a,btype,b,ctype,c
1.系统调用返回值类型 2.系统调用的名称 3. 调用参数类型 名称
*/
#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}
3. 以close展开为例 说明_syscall1(int close int fd)
的宏展开
这里展开的内嵌汇编代码,其实就是, 把系统调用号 参数等 分别赋值给不同的寄存器
1
2
3
4
5
6
7
8
9
10
11
12
int close(int fd) // 返回值为int 参数类型为int
{
long __res; // 定义返回值 为一个长整数
__asm__ volatile ("int $0x80" // 调用0x80中断
: "=a" (__res) // 输出 ex的值赋值给res
: "0" (__NR_close), // 输入 赋值 系统调用号赋值给eax
"b" ((long)(fd))); // 输入 将_NR_name 赋值给ebx
if (__res >= 0)
return (int) __res; //返回
errno = -__res;
return -1;
}
4. 其中 __NR_close 就是系统调用的编号,在 include/unistd.h 中定义:
1
2
3
4
5
6
7
8
9
#ifdef __LIBRARY__
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
5. int 0x80详解 进入内核
- 在内核初始化时,主函数在 init/main.c 中,调用了 sched_init() 初始化函数:
1
2
3
4
5
6
7
8
void main(void)
{
// ……
time_init();
sched_init();
buffer_init(buffer_memory_end);
// ……
}
- sched_init() 在 kernel/sched.c 中定义为:
1
2
3
4
5
void sched_init(void)
{
// ……
set_system_gate(0x80,&system_call); // 将 system_call 函数地址写到 0x80 对应的中断描述符中
}
- set_system_gate 是个宏,在 include/asm/system.h 中定义为:
1
2
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr) // idt 表 addr为地址 3为dpl
- _set_gate
虽然看起来挺麻烦,但实际上很简单,就是填写 IDT(中断描述符表),将 system_call 函数地址写到 0x80 对应的中断描述符中,也就是在中断 0x80 发生后,自动调用函数 system_call。
1
2
3
4
5
6
7
8
9
10
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
"movw %0,%%dx\n\t" \
"movl %%eax,%1\n\t" \
"movl %%edx,%2" \
: \
: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
"o" (*((char *) (gate_addr))), \
"o" (*(4+(char *) (gate_addr))), \
"d" ((char *) (addr)),"a" (0x00080000))
- system_call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
!……
! # 这是系统调用总数。如果增删了系统调用,必须做相应修改
nr_system_calls = 72
!……
.globl system_call
.align 2
system_call:
! # 检查系统调用编号是否在合法范围内
cmpl \$nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx
! # push %ebx,%ecx,%edx,是传递给系统调用的参数
pushl %ebx
! # 让ds, es指向GDT,内核地址空间
movl $0x10,%edx
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx
! # 让fs指向LDT,用户地址空间
mov %dx,%fs
call sys_call_table(,%eax,4) // 根据eax跳转执行 系统调用号 每个函数4个字节
pushl %eax
movl current,%eax
cmpl $0,state(%eax)
jne reschedule
cmpl $0,counter(%eax)
je reschedule
系统调用 以close 为例
1. linux/lib/close.c API
1
2
3
4
#define __LIBRARY__
#include <unistd.h>
_syscall1(int,close,int,fd)
2. _syscall 函数
_syscall
是一个宏,在include/unistd.h
中定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define _syscall0(type,name) \
#define _syscall1(type,name,atype,a) \
#define _syscall2(type,name,atype,a,btype,b) \
/*
每个系统调用 2 + 2*n个参数
type, name, atype,a,btype,b,ctype,c
1.系统调用返回值类型 2.系统调用的名称 3. 调用参数类型 名称
*/
#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}
3. 以close展开为例 说明_syscall1(int close int fd)
的宏展开
这里展开的内嵌汇编代码,其实就是, 把系统调用号 参数等 分别赋值给不同的寄存器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define __NR_close 6
int close(int fd) // 返回值为int 参数类型为int
{
long __res; // 定义返回值 为一个长整数
__asm__ volatile ("int $0x80" // 调用0x80中断
: "=a" (__res) // 输出 ex的值赋值给res
: "0" (__NR_close), // 输入 赋值 系统调用号赋值给eax
"b" ((long)(fd))); // 输入 将_NR_name 赋值给ebx
if (__res >= 0)
return (int) __res; //返回
errno = -__res;
return -1;
}
4. 初始化sys_call_table 表 inx 0x80中断初始化问题等。
略 同上
sys_call_table
表见 include/linux/sys.h
5. sys_call_table 表中函数的实现 以sys_close为例
linux-0.11/fs/open.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int sys_close(unsigned int fd)
{
struct file * filp;
if (fd >= NR_OPEN)
return -EINVAL;
current->close_on_exec &= ~(1<<fd);
if (!(filp = current->filp[fd]))
return -EINVAL;
current->filp[fd] = NULL;
if (filp->f_count == 0)
panic("Close: file count is 0");
if (--filp->f_count)
return (0);
iput(filp->f_inode);
return (0);
}
5. 在liunx-0.11/kernel/who.c 中编写自己的系统调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <asm/segment.h>
#include <errno.h>
#include <string.h>
char _myname[24];
int sys_iam(const char *name)
{
char str[25];
int i = 0;
do
{
// get char from user input
str[i] = get_fs_byte(name + i);
} while (i <= 25 && str[i++] != '\0');
if (i > 24)
{
errno = EINVAL;
i = -1;
}
else
{
// copy from user mode to kernel mode
strcpy(_myname, str);
}
return i;
}
int sys_whoami(char *name, unsigned int size)
{
int length = strlen(_myname);
printk("%s\n", _myname);
if (size < length)
{
errno = EINVAL;
length = -1;
}
else
{
int i = 0;
for (i = 0; i < length; i++)
{
// copy from kernel mode to user mode
put_fs_byte(_myname[i], name + i);
}
}
return length;
}
6. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* iam.c */
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <asm/segment.h>
#include <linux/kernel.h>
_syscall1(int, iam, const char*, name);
int main(int argc, char *argv[])
{
/*调用系统调用iam()*/
iam(argv[1]);
return 0;
}