一、系统调用:
1、由操作系统内核提供,使得用户程序可以访问硬件设备和其它操作系统资源,即为用户空间提供一种硬件的抽象接口;
2、程序中所有与系统有关的代码的实现都必须通过这些接口来完成,保证了系统的安全与稳定;
3、对于用户空间的进程,在一般情况下是通过应用编程接口(API)而不是系统调用来进行编程,有些API往往直接封装了系统调用,
但这并不意味着两者是一一对应的。当前最流行的API是基于POSIX标准的。
4、系统调用涉及的两个文件: /usr/src/linux/include/asm/unistd.h:系统调用号/usr/src/linux/arch/x86/kernel/entry_32.S: 系统调用的汇编实现
二、AT&T嵌入式汇编扫盲
I 、和Intelx86汇编的区别:
1、立即数之前要加$符号;2、 寄存器前要加%;
3、()表示用括号内的值作为地址进行间接寻址;
4、*表示取出该地址下的内容;
5、格式为: 指令 源操作数 -> 目的操作数
II、gnu C语言中嵌入汇编代码的常见格式如下:
__asm__ volatile ("int $0x80": "=a" (__res)
: "0" (__NR_##name), "b" ((long)(arg1)),"c" ((long)(arg2)),
"d" ((long)(arg3)));
各种符号的含义如下:
__asm__: 嵌入汇编标志,语句之间使用“;”、“\n”或“\n\t”分开 ,指令中的操作数可以使用占位符引用C语言变量 %0-%9,
为了与此占位符区分,寄存器前应该加%%,如%%eax;
"=a" (__res): 将调用int 0x80之后寄存器eax作为返回值传给变量__res, 每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数;"0" (__NR_##name):将__NR_与变量name做字符串拼接后传给寄存器eax作为调用int 0x80的参数;
"b" ((long)(arg1)):将变量arg1强转化成long型变量后传给寄存器ebx作为调用int 0x80的参数;
其余的字符与汇编中寄存器之间的关系如下表:
“S”: 寄存器esi “D”: 寄存器edi
“d”: 寄存器edx “c”: 寄存器ecx
三、系统调用过程源码分析:
I、__syscallN()宏,其中N表示参数的个数。
以__syscall1为例:
#define _syscall1(type,name,type1,arg1) \type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \//调用软中断0x80
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1))); \
if (__res >= 0) \
return (type) __res; \//调用成功
errno = -__res; \//调用失败
return -1; \ //返回-1报错 //这里大概就是为什么大多数调用后面都有if(-1 == res)的奥密
}
II、entry.s源码轮廓[hint: 内核版本为2.6.38]
1、首先int 0x80中断会被系统处理,调用到entry(system_call);
2、执行到syscall_call,执行对应的系统调用; //主要任务也是我们关心的任务在此完成。
3、执行syscall_exit,可能跳转到syscall_exit_work;
4、执行syscall_exit_work,可能跳转到work_pending,也可能跳转到resume_userspace;
5、执行resume_userspace,如果还有工作要做则跳转到work_pending;
6、执行work_pending,如果有pending的信号则调用信号处理,然后跳到resume_userspace;
7、执行resume_userspace,如果还有工作要做则跳转到work_pending;
8、执行work_pending,此时没有信号要处理,再看是否还有工作要完成,如果没有了跳到restore_all, 如果还有工作要做,则看是否需要进行进程调度;
9、执行restore_all,最终调用iret指令返回到用户态;