Linux学习--系统调用
---恢复内容开始---
Linux学习—系统调用
操作系统实现系统调用的基本过程:
- 1. 应用程序调用库函数(API)
- 2. API将系统调用号存入EAX,使用int 0x80中断进入内核态
- 3. 内核中的中断处理函数根据系统调用号调用对应的内核函数(系统调用)
- 4. 系统调用完成相应功能,将返回值存入EAX,返回到中断处理函数
- 5. 中断处理函数返回到API
- 6. API将EAX返回给中断处理程序
调用一个普通的自定义函数,就是call到调用的函数的地址去执行
调用系统函数是调用系统库为调用该系统而编写的接口函数API
API的主要工作是:
将系统调用号存入EAX
将函数参数存入其他通用寄存器
触发0x80号中断进入内核
一、API分析
下面以Linux0.11源码lib/close.c为例分析一下API
打开文件之后代码如下:
#define __LIBRARY__ ----使得_syscall1有效
#include <unistd.h> ----编译器才能获知自定义的系统调用的编号
_syscall1(int,close,int,fd) ----宏,在include/unistd.h中定义,展开后如下
其中:__asm__ 表示后面代码是内嵌汇编
volatile 表示编译器不要优化后面代码,保持原样
括号中为汇编代码,功能是:
先将宏NR_close(中断号,在unistd.h中定义)存入EAX,将参数fd存入EBX,然后进行0x80中断调用。调用返回后,从EAX取出返回值,存入res
然后c语言判断res的值做出相应的返回
因此,自己定义API时注意以下:
自己写中断号加入到include/unistd.h中
写类似于close.c的文件,注意1个参数用syscall1,2个参数用syscall2
二、0x80中断处理过程
在init/main.c中用sched_init()初始化中断,其在kernel/sched.c中定义为:
其中set_system_gate为宏,include/asm/system.h中定义为
以上为填写IDT(中断描述符表),将system_call函数地址写到0x80对应的中断描述符中,也就是在中断0x80发生后,自动调用函数system_call。
接下来就是system_call,汇编打造,在kernel/system_call中
system_call用.globl修饰为其他函数可见。
call sys_call_table(,%eax,4)之前是一些压栈保护,修改段寄存器为内核段,
call sys_call_table(,%eax,4)之后是看看是否需要重新调度
对于call sys_call_table(,%eax,4)这一句,根据汇编寻址方法,他实际上是
call sys_call_table + 4 * %eax
其中eax为系统调用号,即__NR_xxxxxx
sys_call_table一定是一个函数指针数组的起始地址,它定义在include/linux/sys.h中:
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,…… 增加实验要求的系统调用,需要在这个函数表中增加两个函数引用——sys_iam和sys_whoami。当然该函数在sys_call_table数组中的位置必须和__NR_xxxxxx的值对应上。同时还要仿照此文件中前面各个系统调用的写法,加上:
extern int sys_whoami(); extern int sys_iam();
三、在内核中实现系统调用函数sys_###
创建一个.c文件,在里面实现sys_###函数就可以了(仿照其他系统调用函数的写法)
四、修改makefile
Makefile里记录的是所有源程序文件的编译、链接规则。我们之所以简单地运行make就可以编译整个代码树,是因为make完全按照Makefile里的指示工作。
需要修改两处:
在后面添加上你所创建的.c文件编译成的.o文件
添加依赖文件
五、用printk()调试内核
printf()函数是在用户态喜爱那向控制台打印信息,在内核态中使用printk()
可以在自己编写的系统调用函数sys_###中printk()一些信息,在用户程序调用的时候就可以知道是否调用sys_###了
六、编写.c程序,但是需要在Linux0.11下编译
使用命令gcc –o ### ###.c –Wall
用###.c来代指自己编写的.c文件
“-Wall”参数是给出所有的编译警告信息
“-o”参数指定生成的执行文件名是###
使用./###来运行程序
七、在用户态和内核态中传递数据
指针参数传递的是应用程序所在地址空间的逻辑地址,在内核中如果直接访问这个地址,访问到的是内核空间中的数据,不会是用户空间的。所以这里还需要一点儿特殊工作,才能在内核中从用户空间得到数据。
以open函数为例:
统调用是用eax、ebx、ecx、edx寄存器来传递参数:eax传递系统调用号,ebx传递第一个参数,ecx传递第二个参数……但是ebx存放的文件指针指向的是用户空间的地址,当在内核空间执行代码时,如何完成数据传递呢?
- open接下来进行系统调用system_call
这里要注意一下AT&T汇编格式与Intel汇编格式的区别
movl为传递long(32位)的数据,源操作数在前,目的操作数在后
可以看出获得用户空间数据靠的是段寄存器fs
然后代码调转到sys_open()
它将参数传给了open_namei()。再沿着open_namei()继续查找,文件名先后又被传给dir_namei()、get_dir()
通过get_fs_byte得到了fs寄存器里面的数据,完成了在内核空间中获取数据空间的数据的任务,同样的put_fs_byte将内核空间的额数据传递到用户空间
put_fs_xxx()和get_fs_xxx()都是用户空间和内核空间之间的桥梁
---恢复内容结束---