主要内容:

  1. 什么是系统调用
  2. Linux上的系统调用实现原理
  3. 一个简单的系统调用的实现

1. 什么是系统调用

简单来说,系统调用就是用户程序和硬件设备之间的桥梁。

用户程序在需要的时候,通过系统调用来使用硬件设备。

系统调用的存在,有以下重要的意义:

1)用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,这样大大简化了用户程序的开发。

比如:用户程序通过write()系统调用就可以将数据写入文件,而不必关心文件是在磁盘上还是软盘上,或者其他存储上。

2)系统调用使得用户程序有更好的可移植性。

只要操作系统提供的系统调用接口相同,用户程序就可在不用修改的情况下,从一个系统迁移到另一个操作系统。

3)系统调用使得内核能更好的管理用户程序,增强了系统的稳定性。

因为系统调用是内核实现的,内核通过系统调用来控制开放什么功能及什么权限给用户程序。

这样可以避免用户程序不正确的使用硬件设备,从而破坏了其他程序。

4)系统调用有效的分离了用户程序和内核的开发。

用户程序只需关心系统调用API,通过这些API来开发自己的应用,不用关心API的具体实现。

内核则只要关心系统调用API的实现,而不必管它们是被如何调用的。

用户程序,系统调用,内核,硬件设备的调用关系如下图:

2. Linux上的系统调用实现原理

要想实现系统调用,主要实现以下几个方面:

  1. 通知内核调用一个哪个系统调用
  2. 用户程序把系统调用的参数传递给内核
  3. 用户程序获取内核返回的系统调用返回值

下面看看Linux是如何实现上面3个功能的。

2.1 通知内核调用一个哪个系统调用

每个系统调用都有一个系统调用号,系统调用发生时,内核就是根据传入的系统调用号来知道是哪个系统调用的。

在x86架构中,用户空间将系统调用号是放在eax中的,系统调用处理程序通过eax取得系统调用号。

系统调用号定义在内核代码:arch/alpha/include/asm/unistd.h 中,可以看出linux的系统调用不是很多。

2.2 用户程序把系统调用的参数传递给内核

系统调用的参数也是通过寄存器传给内核的,在x86系统上,系统调用的前5个参数放在ebx,ecx,edx,esi和edi中,如果参数多的话,还需要用个单独的寄存器存放指向所有参数在用户空间地址的指针。

一般的系统调用都是通过C库(最常用的是glibc库)来访问的,Linux内核提供一个从用户程序直接访问系统调用的方法。

参见内核代码:arch/cris/include/arch-v10/arch/unistd.h

里面定义了6个宏,分别可以调用参数个数为0~6的系统调用

_syscall0(type,name)
_syscall1(type,name,type1,arg1)
_syscall2(type,name,type1,arg1,type2,arg2)
_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)
_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)

超过6个参数的系统调用很罕见,所以这里只定义了6个。

2.3 用户程序获取内核返回的系统调用返回值

获取系统调用的返回值也是通过寄存器,在x86系统上,返回值放在eax中。

3. 一个简单的系统调用的实现

了解了Linux上系统调用的原理,下面就可以自己来实现一个简单的系统调用。

3.1 环境准备

为了不破坏现有系统,我是用虚拟机来实验的。

主机:fedora16 x86_64系统 + kvm(一种虚拟技术,就像virtualbox,vmware等)

虚拟机: 也是安装fedora16 x86_64系统(通过virt-manager很容易安装一个系统)

下载内核源码:www.kernel.org  下载最新的就行

3.2 修改内核源码中的相应文件

主要修改以下文件:

arch/x86/ia32/ia32entry.S
arch/x86/include/asm/unistd_32.h
arch/x86/include/asm/unistd_64.h
arch/x86/kernel/syscall_table_32.S
include/asm-generic/unistd.h
include/linux/syscalls.h
kernel/sys.c

我在sys.c中追加了2个函数:sys_foo和sys_bar

如果是在x86_64的内核中增加一个系统调用,只需修改 arch/x86/include/asm/unistd_64.h,比如sys_bar。

修改内容参见下面的diff文件:

diff -r new/arch/x86/ia32/ia32entry.S old/arch/x86/ia32/ia32entry.S
855d854
< .quad sys_foo
diff -r new/arch/x86/include/asm/unistd_32.h old/arch/x86/include/asm/unistd_32.h
357d356
< #define __NR_foo 349
361c360
< #define NR_syscalls 350
---
> #define NR_syscalls 349
diff -r new/arch/x86/include/asm/unistd_64.h old/arch/x86/include/asm/unistd_64.h
689,692d688
< #define __NR_foo 312
< __SYSCALL(__NR_foo, sys_foo)
< #define __NR_bar 313
< __SYSCALL(__NR_bar, sys_bar)
diff -r new/arch/x86/kernel/syscall_table_32.S old/arch/x86/kernel/syscall_table_32.S
351d350
< .long sys_foo
diff -r new/include/asm-generic/unistd.h old/include/asm-generic/unistd.h
694,695d693
< #define __NR_foo 272
< __SYSCALL(__NR_foo, sys_foo)
698c696
< #define __NR_syscalls 273
---
> #define __NR_syscalls 272
diff -r new/kernel/sys.c old/kernel/sys.c
1920,1928d1919
<
< asmlinkage long sys_foo(void)
< {
< return 1112223334444555;
< }
< asmlinkage long sys_bar(void)
< {
< return 1234567890;
< }

3.3 编译内核

#cd linux-3.2.28
#make menuconfig (选择要编译参数,如果不熟悉内核编译,用默认选项即可)
#make all (这一步真的时间很长......)
#make modules_install
#make install (这一步会把新的内核加到启动项中)
#reboot (重启系统进入新的内核)

3.4 编写调用的系统调用的代码

#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#include <stdio.h>
#include <errno.h> #define __NR_foo 312
#define __NR_bar 313 int main()
{
printf ("result foo is %ld\n", syscall(__NR_foo));
printf("%s\n", strerror(errno));
printf ("result bar is %ld\n", syscall(__NR_bar));
printf("%s\n", strerror(errno));
return 0;
}

编译运行上面的代码:

#gcc test.c -o test
#./test

运行结果如下:

result foo is 1112223334444555
Success
result bar is 1234567890
Success

《Linux内核设计与实现》读书笔记(五)- 系统调用的更多相关文章

  1. Linux内核设计与实现 读书笔记 转

    Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...

  2. Linux内核设计与实现 读书笔记

    第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的ta ...

  3. Linux内核设计与实现读书笔记(8)-内核同步方法【转】

    转自:http://blog.chinaunix.net/uid-10469829-id-2953001.html 1.原子操作可以保证指令以原子的方式执行——执行过程不被打断.内核提供了两组原子操作 ...

  4. Linux内核设计与实现——读书笔记2:进程管理

    1.进程: (1)处于执行期的程序,但不止是代码,还包括各种程序运行时所需的资源,实际上进程是正在执行的 程序的实时结果. (2)程序的本身并不是进程,进程是处于执行期的程序及其相关资源的总称. (3 ...

  5. Linux内核设计与实现——读书笔记1:内核简介

    内核:有的时候被称管理者或者操作系统核心,通常内核负责响应中断的中断服务程序, 负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间德内存管理程序 和网络,进程间通信等系统服务程序共同组 ...

  6. 《Linux内核设计与实现》第五周读书笔记——第十一章

    <Linux内核设计与实现>第五周读书笔记——第十一章 20135301张忻 估算学习时间:共2.5小时 读书:2.0 代码:0 作业:0 博客:0.5 实际学习时间:共3.0小时 读书: ...

  7. 《Linux内核设计与实现》第五章学习笔记

    <Linux内核设计与实现>第五章学习笔记 姓名:王玮怡  学号:20135116 一.与内核通信     在Linux中,系统调用是用户空间访问内核的唯一手段:除异常和陷入外,它们是内核 ...

  8. 初探内核之《Linux内核设计与实现》笔记上

    内核简介  本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核   原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单.2. 高效 ...

  9. 【读书笔记】Linux内核设计与实现(第五章)

    5.1 内核通信 系统调用在用户空间和硬件设备之间添加了一个中间层. 该层主要作用: 1.为用户空间提供了一种硬件的抽象接口. 2.保证了系统的稳定和安全. 3.每个进程都运行在虚拟系统中. 在Lin ...

  10. 《Linux内核设计与实现》第五章读书笔记

    第五章  系统调用 5.1与内核通信 1. 系统调用 让应用程序受限的访问硬件设备 提供创建新进程并与已有进程通信的机制 提供申请操作系统其他资源能力是用户空间进程和硬件设备之间的中间层 2. 系统调 ...

随机推荐

  1. java 实现插入排序

    sorted数组第0个位置没有放数据 从sorted数组的第二个数据开始处理 package com.learn; public class InsertionSort { public static ...

  2. 【转载】BusyBox 简化嵌入式 Linux 系统

    原文网址:http://www.ibm.com/developerworks/cn/linux/l-busybox/ BusyBox 是很多标准 Linux® 工具的一个单个可执行实现.BusyBox ...

  3. Web中的安全性问题

    根据2010年OWASP发布的Web应用程序安全风险主要是SQL注入攻击.跨网站脚本.伪造客户端请求.Cookie盗取,传输层保护不足. 1 SQL注入攻击  随着B/S框架结构在系统开发中的广泛应用 ...

  4. Thread之三:Thread Join()的用法

    一.join用法 join()和wait()不会释放锁,join()是Thread的方法,wait()是Object的方法 1.join方法定义在Thread类中,则调用者必须是一个线程 例如: Th ...

  5. L2-005. 集合相似度(set使用)

    L2-005. 集合相似度 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定两个整数集合,它们的相似度定义为:Nc/Nt*1 ...

  6. Java-API:javax.servlet.http.HttpServletResponse

    ylbtech-Java-API:javax.servlet.http.HttpServletResponse 1.返回顶部 1. javax.servlet.http Interface HttpS ...

  7. SQL 从身份证号得到出生日期、年龄、男女

    ), CONVERT(smalldatetime, SUBSTRING(b.IDCard, , )), ) AS BrithDate_Name, DATEDIFF(year, CONVERT(smal ...

  8. Oracle 内存结构

     内存结构 Oracle内存,进程与数据库的图 sga:系统全局区,用来存放操作的数据,库缓存,数据字典等控制信息的内存区域, pga:进程全局区,服务进程专用的内存区域,大多数内容非共享 uga:用 ...

  9. PHP实现常用排序算法(含示意动图)

    目录 1 快速排序 2 冒泡排序 3 插入排序 4 选择排序 5 归并排序 6 堆排序 7 希尔排序 8 基数排序 总结 作为phper,一般接触算法的编程不多. 但基本的排序算法还是应该掌握. 毕竟 ...

  10. redis学习四 复制

    1,单机创建多实例 一个redis服务器安装多个redis实例,每个实例对应一个端口.默认端口是6379. 将redis.conf配置文件复制一份到另外一个文件夹下,然后修改其中的信息即可.   pi ...