【版权所有,转载请注明出处。出处: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. CSS中div覆盖另一个div

    将一个div覆盖在另一个div上有两种手段:一是设置margin为负值,二是设置绝对定位. 可以根个人情况设置z-index的值 1->position 为absolute的情况 <htm ...

  2. 由position属性引申的关于css的进阶讨论(包含块、BFC、margin collapse)

    写这篇文章的起因是源于这篇文章:谈谈面试与面试题 中关于position的讨论,文中一开始就说的这句话: 面试的时候问个css的position属性能刷掉一半的人这是啥情况…… 其实这问题我本来打算的 ...

  3. IEE重建表完全释放磁盘空间具体步骤参考

    环境:RHEL 5.3 + IEE 5.1.40 本文目的:指导项目侧人员再遇到此类改动需求时可以自己更改.需求:mr_intrainterfreq表重建,历史数据全部删掉. 1.停库: 1.1确认现 ...

  4. 有了jsRender,妈妈再也不用担心我用jq拼接DOM拼接的一团糟了、页面整齐了、其他伙伴读代码也不那么费劲了

    写在前面 说来也很巧, 下午再做一个页面,再普通不过的分页列表,我还是像往常一样,基于MVC环境下,我正常用PagedList.MVC AJAX做无刷新分页,这时候问题就来了,列表数据中有个轮播图用到 ...

  5. 给ubuntu中的软件设置desktop快捷方式(以android studio为例)

    ubuntu的快捷方式都在/usr/share/applications/路径下有很多*.desktop(eclipse的快捷方式也可以类似设置) 下面就建立我们的studio sudo gedit ...

  6. Access数据库多表连接查询

    第一次在Access中写多表查询,就按照MS数据库中的写法,结果报语法错,原来Access的多表连接查询是不一样的 表A.B.C,A关联B,B关联C,均用ID键关联 一般写法:select * fro ...

  7. 2014/11/06 Oracle触发器初步 2014-11-06 09:03 49人阅读 评论(0) 收藏

    触发器我就不多解释了,保证数据的完整性的神器,嗯..也是减少程序员工作托管给数据库操作的好帮手.就不讲一些大道理了.通俗点,我们对数据库的操作,无非就是增 删 改 查. 触发器就是在删,改,增的时候( ...

  8. jQuery-1.9.1源码分析系列(十) 事件系统——主动触发事件和模拟冒泡处理

    发现一个小点,先前没有注意的 stopPropagation: function() { var e = this.originalEvent; ... if ( e.stopPropagation ...

  9. 为 Web 设计师准备的 20 款 CSS3 工具

    1.CSS3 Generator 2. Border Radius 3. CSS3 Maker 4. CSS3 Transforms 5. CSS3 Drop shadow generator 6. ...

  10. Win10 UWP 开发系列:使用SQLite

    在App开发过程中,肯定需要有一些数据要存储在本地,简单的配置可以序列化后存成文件,比如LocalSettings的方式,或保存在独立存储中.但如果数据多的话,还是需要本地数据库的支持.在UWP开发中 ...