【版权所有,转载请注明出处。出处: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. Android图片缓存之Bitmap详解

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. 图片缓存相关博客地址: Android图片缓 ...

  2. 详解 ML2 Core Plugin(I) - 每天5分钟玩转 OpenStack(71)

    我们在 Neutron Server 小节学习到 Core Plugin,其功能是维护数据库中 network, subnet 和 port 的状态,并负责调用相应的 agent 在 network ...

  3. Web APi之认证(Authentication)及授权(Authorization)【一】(十二)

    前言 无论是ASP.NET MVC还是Web API框架,在从请求到响应这一过程中对于请求信息的认证以及认证成功过后对于访问页面的授权是极其重要的,用两节来重点来讲述这二者,这一节首先讲述一下关于这二 ...

  4. etlpy: 并行爬虫和数据清洗工具(开源)

    etlpy是python编写的网页数据抓取和清洗工具,核心文件etl.py不超过500行,具备如下特点 爬虫和清洗逻辑基于xml定义,不需手工编写 基于python生成器,流式处理,对内存无要求 内置 ...

  5. ORM开发之解析lambda实现group查询(附测试例子)

    目的:以编程方式实现group查询,在开发ORM时,需要达到这样的效果 先看一个简单的group语句 select BarCode,ProductName,COUNT(BarCode) as tota ...

  6. Android之探究viewGroup自定义子属性参数的获取流程

    通常会疑惑,当使用不同的布局方式时,子view得布局属性就不太一样,比如当父布局是LinearLayout时,子view就能有效的使用它的一些布局属性如layout_weight.weightSum. ...

  7. Hadoop入门学习笔记---part2

    在<Hadoop入门学习笔记---part1>中感觉自己虽然总结的比较详细,但是始终感觉有点凌乱.不够系统化,不够简洁.经过自己的推敲和总结,现在在此处概括性的总结一下,认为在准备搭建ha ...

  8. Hibernate ——二级缓存

    一.Hibernate 二级缓存 1.Hibernate 二级缓存是 SessionFactory 级别的缓存. 2.二级缓存分为两类: (1)Hibernate内置二级缓存 (2)外置缓存,可配置的 ...

  9. 在IIS7.5中ASP.NET调用cmd程序拒绝访问决绝方法小记

    前言 昨天利用Github的Webhook实现自动部署站点,其中要调用命令行(cmd.exe)程序执行shell脚本. 在本地测试没有任何问题,部署到服务器之后,发现错误信息:访问拒绝. 问题 没有权 ...

  10. Xenocode Postbuild 2010 for .NET 使用说明

    文章转载自网络 用法一: .导入要加密的dotNET程序或assembly文件(.dll/.exe) .选择第二个选项卡“Protect” .点击“Select Pattern” .选中所有“Obje ...