【版权所有,转载请注明出处。出处:http://www.cnblogs.com/joey-hua/p/5570691.html 】

Linux内核从启动到初始化也看了好些个源码文件了,这次看到kernel文件夹下的system_call.s,这个文件主要就是系统调用的过程。但说到系统调用,不只是这一个文件这么简单,里面牵扯到的内容太多,这里就做个笔记记录一下从建立中断到最终调用系统调用的完整机制。

假设就从write这个函数作为系统调用来解释。

系统调用的本质就是用户进程需要访问内核级别的代码,但用户进程的权限是最低的,内核代码是权限最高的,不允许直接访问,需要通过中断门作为媒介来实现权限的跳转。简单讲就是用户进程调用一个中断,这个中断再去访问内核代码。这里就来学习一下Linux内核具体是怎么做的。

1.建立中断描述符表IDT

因为要用到中断,所以首先要建立中断描述符表IDT,作用如下图:

在head.s文件中,建立好了IDT,比如要使用int 0x80,就从_idt开始找到偏移为0x80的地方执行代码。

	.align 3						# 按8 字节方式对齐内存地址边界。
_idt: .fill 256,8,0 # idt is uninitialized# 256 项,每项8 字节,填0。 idt_descr: #下面两行是lidt 指令的6 字节操作数:长度,基址。
.word 256*8-1 # idt contains 256 entries
.long _idt lidt idt_descr # 加载中断描述符表寄存器值。

2.建立0x80号中断

所有的系统调用都是通过0x80号中断来实现的,所以接下来就是建立第0x80号中断,在sched.c中:

// 设置系统调用中断门。
set_system_gate (0x80, &system_call);

这里通过set_system_gate这个宏定义就把0x80中断和函数system_call关联上了,这里先不管system_call,先看set_system_gate,在system.h中:

//// 设置系统调用门函数。
// 参数:n - 中断号;addr - 中断程序偏移地址。
// &idt[n]对应中断号在中断描述符表中的偏移值;中断描述符的类型是15,特权级是3。
#define set_system_gate(n,addr) _set_gate(&idt[n],15,3,addr) //// 设置门描述符宏函数。
// 参数:gate_addr -描述符地址;type -描述符中类型域值;dpl -描述符特权层值;addr -偏移地址。
// %0 - (由dpl,type 组合成的类型标志字);%1 - (描述符低4 字节地址);
// %2 - (描述符高4 字节地址);%3 - edx(程序偏移地址addr);%4 - eax(高字中含有段选择符)。
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ( "movw %%dx,%%ax\n\t" \ // 将偏移地址低字与选择符组合成描述符低4 字节(eax)。
"movw %0,%%dx\n\t" \ // 将类型标志字与偏移高字组合成描述符高4 字节(edx)。
"movl %%eax,%1\n\t" \ // 分别设置门描述符的低4 字节和高4 字节。
"movl %%edx,%2":
:"i" ((short) (0x8000 + (dpl << 13) + (type << 8))),
"o" (*((char *) (gate_addr))),
"o" (*(4 + (char *) (gate_addr))), "d" ((char *) (addr)), "a" (0x00080000))

这里参考中断门结构图可知,这里设置特权级是3,用户进程也是3,就可以直接访问此中断,偏移地址对应的上面的system_call,也就是说如果调用中断int 0x80,那么就会去访问system_call函数。注意这里的n就是0x80,也就是idt数组的[0x80],idt在head.h中声明,编译后会变成符号_idt,在head.s中定义的,就此关联上。

3.声明系统调用函数

以write系统函数为例,在write.c中声明此函数:

_syscall3 (int, write, int, fd, const char *, buf, off_t, count)

_syscall3又是一个宏定义,在unistd.h中:

// 有3 个参数的系统调用宏函数。type name(atype a, btype b, ctype c)
// %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a),%3 - ecx(b),%4 - edx(c)。
#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) \
: "" (__NR_##name), "b" ((long)(a)), "c" ((long)(b)), "d" ((long)(c))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}

所以翻译过来就是在write.c中可以写成:

int write(int fd,const char* buf,off_t count) \
{ \
long __res; \
__asm__ volatile ( "int $0x80" \
: "=a" (__res) \
: "" (__NR_write), "b" ((long)(fd)), "c" ((long)(buf)), "d" ((long)(count))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}

是不是一下子就清晰明朗了,也就是说,如果一个用户进程要使用write函数,就会去调用int 0x80中断,然后把三个参数fd、buf、count分别存入ebx、ecx、edx寄存器,还有个最关键的是_NR_write,会把这个值存入eax寄存器,具体做什么用等会再说,这个是在unistd.h中定义的:

#define __NR_write 4

好,现在各种初始化和声明都完成了,万事俱备只欠东风!

4.系统调用过程

用户进程调用函数write,就会调用int 0x80中断,上面第2点已经说了,如果调用中断int 0x80会去访问system_call函数,sched.c:

extern int system_call (void);	// 系统调用中断处理程序(kernel/system_call.s,80)。

是在system_call中定义,注意编译后头部会加上_,以下代码只截取了前半部分:

_system_call:
cmpl $nr_system_calls-1,%eax # 调用号如果超出范围的话就在eax 中置-1 并退出。
ja bad_sys_call
push %ds # 保存原段寄存器值。
push %es
push %fs
pushl %edx # ebx,ecx,edx 中放着系统调用相应的C 语言函数的调用参数。
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds # ds,es 指向内核数据段(全局描述符表中数据段描述符)。
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs # fs 指向局部数据段(局部描述符表中数据段描述符)。
# 下面这句操作数的含义是:调用地址 = _sys_call_table + %eax * 4。参见列表后的说明。
# 对应的C 程序中的sys_call_table 在include/linux/sys.h 中,其中定义了一个包括72 个
# 系统调用C 处理函数的地址数组表。
call _sys_call_table(,%eax,4)
pushl %eax # 把系统调用号入栈。(这个解释错误,是函数返回值入栈)
movl _current,%eax # 取当前任务(进程)数据结构地址??eax。

注意从pushl %edx开始的三句代码,是前面第3点提到的三个参数依次从右向左入栈。重点是call _sys_call_table(,%eax,4)这句代码,翻译过来就是call [eax*4 + _sys_call_table],根据第3点,eax存的是_NR_write的值也就是4,因为_sys_call_table是sys.h中的一个int (*)()类型的数组,里面存的是所有的系统调用函数地址,所以再翻译一下就是访问sys_call_table[4]也就是sys_write函数:

// 系统调用函数指针表。用于系统调用中断处理程序(int 0x80),作为跳转表。
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, ...}

sys_write在fs下的read_write.c:

int
sys_write (unsigned int fd, char *buf, int count)
{
struct file *file;
struct m_inode *inode;
...
}

好了,到这里为止才明白千回百转最终调用的就是这个sys_write函数。至此分析结束!

Linux0.11内核--系统调用机制分析的更多相关文章

  1. Linux0.11内核--fork进程分析

    [版权所有,转载请注明出处.出处:http://www.cnblogs.com/joey-hua/p/5597818.html ] 据说安卓应用里通过fork子进程的方式可以防止应用被杀,大概原理就是 ...

  2. Linux0.11内核--缓冲区机制大致分析

    文件系统的文件太多,而且是照搬的MINIX的文件系统,不想继续分析下去了.缓冲区机制和文件系统密切相关,所以这里就简单分析一下缓冲区机制. buffer.c 程序用于对高速缓冲区(池)进行操作和管理. ...

  3. Linux-0.11内核源代码分析系列:内存管理get_free_page()函数分析

    Linux-0.11内存管理模块是源码中比較难以理解的部分,如今把笔者个人的理解发表 先发Linux-0.11内核内存管理get_free_page()函数分析 有时间再写其它函数或者文件的:) /* ...

  4. Linux0.11内核--进程调度分析之1.初始化

    [版权所有,转载请注明出处.出处:http://www.cnblogs.com/joey-hua/p/5596746.html ] 首先看main.c里的初始化函数main函数里面有个函数是对进程调度 ...

  5. linux0.11内核源码剖析:第一篇 内存管理、memory.c【转】

    转自:http://www.cnblogs.com/v-July-v/archive/2011/01/06/1983695.html linux0.11内核源码剖析第一篇:memory.c July  ...

  6. linux0.11下的中断机制分析

    http://orbt.blog.163.com/     异常就是控制流中的突变,用来响应处理器状态中的某些变化.当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用, ...

  7. Linux0.11内核剖析--内核体系结构

    一个完整可用的操作系统主要由 4 部分组成:硬件.操作系统内核.操作系统服务和用户应用程序,如下图所示: 用户应用程序是指那些字处理程序. Internet 浏览器程序或用户自行编制的各种应用程序: ...

  8. Linux0.11内核源码——内核态线程(进程)切换的实现

    以fork()函数为例,分析内核态进程切换的实现 首先在用户态的某个进程中执行了fork()函数 fork引发中断,切入内核,内核栈绑定用户栈 首先分析五段论中的第一段: 中断入口:先把相关寄存器压栈 ...

  9. linux0.11内核源码——进程各状态切换的跟踪

    准备工作 1.进程的状态有五种:新建(N),就绪或等待(J),睡眠或阻塞(W),运行(R),退出(E),其实还有个僵尸进程,这里先忽略 2.编写一个样本程序process.c,里面实现了一个函数 /* ...

随机推荐

  1. 从零开始编写自己的C#框架(9)——数据库设计与创建

    对于千万级与百万级数据库设计是有所区别的,由于本项目是基于中小型软件开发框架来设计,记录量相对会比较少,所以数据库设计时考虑的角度是:与开发相结合:空间换性能:空间换开发效率:减少null异常.... ...

  2. SubSonic3.0 Demo1.0——应用了T4模版可减少开发过程中70%以上的代码量以及80%以上的出错率

    应网友的要求,抽了点时间写了这个Demo,希望对2.2版想升级到3.0的朋友或正在使用3.0的朋友有所帮助.大家在使用Demo过程中如果发现什么问题或有什么建议,可以直接将Bug提交给我或告诉我,我会 ...

  3. ARC内存管理机制详解

    ARC在OC里面个人感觉又是一个高大上的牛词,在前面Objective-C中的内存管理部分提到了ARC内存管理机制,ARC是Automatic Reference Counting---自动引用计数. ...

  4. C语言之链表

    这两天在复习C语言的知识,为了给下个阶段学习OC做准备,以下的代码的编译运行环境是Xcode5.0版本,写篇博文把昨天复习的C语言有关链表的知识给大家分享一下,以下是小菜自己总结的内容,代码也是按照自 ...

  5. 【记录】Install-Package : “Unity”已拥有为“CommonServiceLocator”定义的依赖项。

    在使用 NuGet 安装 Unity 的时候,安装命令:install-package unity. 但是会莫名奇妙的报如下错误: “Unity”已拥有为“CommonServiceLocator”定 ...

  6. Oracle 10g安全加固(审计、监听密码)

    环境: Linux 6.4 + Oracle 10.2.0.4 1. Oracle 10g 审计功能 2. 对数据库监听器的关闭和启动设置密码 1. Oracle 10g 审计功能 Oracle 10 ...

  7. VS Extract Method

    前言 看重构6.4Replace Temp with Query(以查询取代临时变量)中提到Replace Temp with Query往往是你运用Extract Method之前必不可少的一个步骤 ...

  8. NPTL vs PThread

    NPTL vs PThread POSIX threads (pthread) is not an implementation, it is a API specification (a stand ...

  9. 图片上传插件ImgUploadJS:用HTML5 File API 实现截图粘贴上传、拖拽上传

    一 . 背景及效果 当前互联网上传文件最多的就是图片文件了,但是传统web图片的截图上传需要:截图保存->选择路径->保存后再点击上传->选择路径->上传->插入. 图片 ...

  10. 注册表(C#)

    Windowa注册表是包含Windows安装,用户喜好以及以安装软件和设备的所有配置信息的核心储存库.COM组件必须把它的信息出存在注册表中,才能被客户程序使用.注册表也包含了一些系统配置的信息,这些 ...