1、参考资料

  • 赵炯博士的网站oldlinux
  • Linux内核完全注释
  • Linux0.11 源码

2、概要

操作系统作为软件应用层和底层硬件之间的部分,向下提供服务,向上提供接口。系统调用便是操作系统向上层应用提供的操作底层硬件的和核心服务的接口,也就是API(Application Programming Interface)。我们知道一般API实现的方法是提供函数接口,只需要调用函数就可以实现相应的功能,底层的原理是程序地址的跳转。因为操作系统和用户程序同时存在于内存中,我们当然是不希望操作系统的数据被随意的篡改和访问,有可能造成十分严重的后果,所以操作系统对内存做了区分:

  • 内核态(0)
  • 服务态(1,2)
  • 用户态(3)

数值越小,级别越高,低级别进程无权访问高级别的内存区域,由此隔离了系统程序和用户程序,提高了操作系统的安全性。

因为CS:IP表示当前指令,所以用CS最低两位(CPL)来表示当前程序属于用户态还是核心态,访问的数据段DS最低两位(DPL)表示目标代码属于用户态还是核心态,在地址跳转时检查DPL和CPL,只有在CPL≤DPL时才允许跳转。但是对于系统调用来说,就需要找到一种方法穿透用户态和内核态的屏障,在x86处理器中,当用户代码想要调用内核代码时,硬件通过终端指令int将CPL改为0,从而穿透屏障实现调用。

3、系统调用write原理

选取write(…)指令进行分析,最典型调用write(…)的命令就是c中的printf(…)函数。write(…)定义在lib/write.c

首先看_syscall3的定义:

/**  linux/lib/write.c**  (C) 1991  Linus Torvalds*/#define __LIBRARY__
#include <unistd.h>_syscall3(int,write,int,fd,const char *,buf,off_t,count)

_syscall3是一个宏,定义在include/unistd.h中,可以发现其中还有多个相似的_syscallx宏,x表示可传入的参数个数

#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) \: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \return (type) __res; \
errno=-__res; \
return -1; \
}

展开后可以看到是将传入宏的参数替换成了一个函数定义,主要内容是将传入的参数存入各个寄存器,之后调用了int 80的中断。其中__NR_##name被传入了eax,因为所有的调用都通过80号中断,显然这是区分不同函数的参数。同时发现在unistd.h中还定义了一系列类似的宏

#ifdef __LIBRARY__
...
#define __NR_write  4
...
#endif

所以可以看出这是对系统调用的索引,而真正起作用的函数定义在fs/read_write.c中

int sys_write(unsigned int fd,char * buf,int count)
{struct file * file;struct m_inode * inode;if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))return -EINVAL;if (!count)return 0;inode=file->f_inode;if (inode->i_pipe)return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;if (S_ISCHR(inode->i_mode))return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos);if (S_ISBLK(inode->i_mode))return block_write(inode->i_zone[0],&file->f_pos,buf,count);if (S_ISREG(inode->i_mode))return file_write(inode,file,buf,count);printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);return -EINVAL;
}

可以看到sys_write(…)的函数原型与之前宏展开的一致,所以在调用write(…)后,实际是调用了sys_write(…),要弄清这个原理,就要去看int 80的实现。

在init/main.c中调用了sched_init(),sched_init()中有一句

set_system_gate(0x80,&system_call);

出现了80号中断,这是一句宏,在include/asm/system.h中,展开得

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \     //将偏移地址低字与选择符组合成低4字节(eax)"movw %0,%%dx\n\t" \          //将类型标识字与偏移高字组合成描述符高4字节"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))...
#define set_system_gate(n,addr) \_set_gate(&idt[n],15,3,addr)
...


idt是中断向量表基址,这里指定第80号中断,DPL被指定为3,同时在system_call为”处理函数入口点偏移“,之后进入kernel/system_call.s的system_call

...
nr_system_calls = 74
...
.globl system_call
...
system_call:cmpl $nr_system_calls-1,%eaxja bad_sys_callpush %dspush %espush %fspushl %edxpushl %ecx      # push %ebx,%ecx,%edx as parameterspushl %ebx      # to the system callmovl $0x10,%edx        # set up ds,es to kernel spacemov %dx,%dsmov %dx,%esmovl $0x17,%edx        # fs points to local data spacemov %dx,%fscall sys_call_table(,%eax,4)pushl %eaxmovl current,%eaxcmpl $0,state(%eax)        # statejne reschedulecmpl $0,counter(%eax)      # counterje reschedule

nr_system_calls表示总系统调用数,所以最先比较eax中的调用号是否小于总数,之后将各个参数推入堆栈,之后调用地址为sys_call_table + %eax × 4处的函数。sys_call_table在include/linux/sys.h中里面有包括sys_write在内的72个系统调用函数的函数地址表。

    ...extern int sys_write();...fn_ptr sys_call_table[] = { ...
sys_write, ...};

而sys_write()就是索引为4的函数。由此系统调用的过程结束,总结为

4、添加调用printval

首先在include/unistd.h中添加系统调用编号:

#define __NR_printval   72

include/linux/sys.h中添加:

extern int sys_printval();fn_ptr sys_call_table[] = {..., sys_printval };

kernel/system_call.s中修改nr_system_calls的值 :

nr_system_calls = 73

kernel/sys.c中实现系统调用函数:

int sys_printval()
{printk("in sys_printval\n");return 0;
}

至此系统调用添加完成,重新编译,并启动内核,接下来我们在用户态编写测试代码。

5、用户态测试代码

修改系统头文件 /usr/include/unistd.h ,增加:

#define __NR_printval   72

编写用户态测试代码main.c

#define __LIBRARY__
#include <stdio.h>
#include <unistd.h>_syscall0(int, printval);int main()
{printf("Hello World!\n");printval(); return 0;
}

编译运行:

# gcc main.c
# ./a.out
Hello World!
in sys_printval

参考:
Linux-0.11内核学习笔记【2】:添加系统调用
Linux-0.11源代码

Linux-0.11内核学习-添加系统调用相关推荐

  1. linux 0.11 内核学习 -- bootsect.s, 万里长征第一步

    呵呵,终于将linux 0.11 下面的boot文件夹下的三个文件读完,下面是相关注释,没有汇编基础的人也是可以读的.废话少说,下面就是linux的源码了. 参考资料 Linux内核完全注释.pdf ...

  2. linux 0.11 内核学习路线

    转载至 http://tieba.baidu.com/p/4871637101 当初一开始拿到赵炯的书时是兴奋的,代码几乎每行都有注释,心想这不手到擒来的吗.但是代码看到十几行就看不下去了,没错就是十 ...

  3. linux 0.11 内核学习 -- console.c,控制台

    参考<linux内核完全注释>和网上相关文章 /* * 控制台显示操作 */ /* *  linux/kernel/console.c * *  (C) 1991  Linus Torva ...

  4. linux内核态串口读写程序,linux 0.11 内核学习 -- rs_io.s,串口汇编代码

    /* *  该文件实现rs232 串行通信中断处理 */ /* *  linux/kernel/rs_io.s * *  (C) 1991  Linus Torvalds */ /* *rs_io.s ...

  5. linux 0.11 内核学习 -- rs_io.s,串口汇编代码

    /* *  该文件实现rs232 串行通信中断处理 */ /* *  linux/kernel/rs_io.s * *  (C) 1991  Linus Torvalds */ /* * rs_io. ...

  6. Linux 0.11内核分析04:多进程视图

    目录 1 进程概念的引入 1.1 使用CPU的直观想法 1.2 直观用法的缺点 1.3 直观用法的改进 1.4 进程的概念 1.4.1 保存程序执行状态 1.4.2 进程与PCB 1.5 Linux ...

  7. Linux 0.11内核分析01:概述

    目录 1. 什么是操作系统 1.1 计算机硬件组成 1.2 操作系统基本结构 2. 操作系统核心视图 2.1 多进程视图 2.1.1 操作系统的相关演变 2.1.2 核心思想 2.2 文件视图 2.2 ...

  8. Linux 0.11内核分析02:系统启动

    目录 1. 内核镜像的构建 1.1 内核源码结构 1.1.1 boot 1.1.2 fs 1.1.3 include 1.1.4 init 1.1.5 kernel 1.1.6 lib 1.1.7 m ...

  9. Linux 0.11内核分析03:系统调用

    目录 1 概述 1.1 什么是系统调用 1.2 为什么需要系统调用 2 系统调用基础设施 2.1 安装系统门 2.1.1 中断描述符 2.1.2 中断描述符安装函数 2.1.3 安装0x80系统门 2 ...

最新文章

  1. React UI 库:React Suite 3.7.8 版本更新
  2. vant UI库组件, 与HTML 标签冲突
  3. 数据流和十六进制转换
  4. 数据结构:基数排序(Radix sort)
  5. 接口测试基础之入门篇(待续)
  6. linux中如何在文件中查找文件,linux下find(文件查找)命令的用法总结
  7. 互联网女皇报告:拼多多美团崛起,支付宝微信同台竞技!
  8. it's just the beginning
  9. 如何在网站中使用php,如何在网站的所有其他PHP文件中包含PHP文件?
  10. 海康RTSP客户端连接深入分析
  11. gurobi和python_Gurobi Python建模环境使用介绍 第一部分准备 (v12最后更新2012
  12. 《刘毅突破英文词汇3000》Vocabulary fundamental 分课音频 下载
  13. 【IDE工具】win10电脑设置保护眼睛色
  14. 社会化分享(附源码)
  15. Python量化交易学习笔记(50)——程序化交易1
  16. 这个用PHP开发的全开源商城系统可免费商用
  17. html border线条重叠,关于border边框重叠颜色设置问题
  18. html汉字间的间距,div字间距-div内文字之间间距设置方法
  19. 单链表的逆转:(头尾互换)
  20. PHP是专为后端,后端开发PHP入门必备

热门文章

  1. Linux命令之mv
  2. SQL Server 高可用方案
  3. 计算机操作系统学习笔记_6_进程管理 --死锁
  4. 经典SQL回顾之晋级篇
  5. SQL Servr 2008空间数据应用系列一:空间信息基础
  6. 关于ASPNET_Membership用户被锁的解决
  7. ZT:Java代码编写的30条建议
  8. AI加持,华为云视频服务助力企业直播行业
  9. 证监会依法对4宗案件作出行政处罚
  10. 为应用程序池**提供服务的进程意外终止。进程ID是**。进程退出代码是'0x80'