【目的】
理解系统调用的概念,熟悉系统调用的用法。
【环境】
PC:ubuntu 12.04
内核:Linux:3.2.0
【要求】
编程创建系统调用sunpluscall(),实现功能是显示字符串到屏幕上。
编译3.2.0内核,用新内核引导系统。
编程调用自己创建的系统调用。
【原理】
操作系统是用户与计算机之间的接口,用户通过操作系统的帮助,可以快速、有效和安全可靠地使用计算机系统中的各种资源来解决自己的问题。为了使用户方便的使用操作系统,OS向用户提供了“用户与操作系统的接口”。这种接口支持用户与操作系统之间进行交互,这些接口可以被分为命令和程序接口两种。前者直接提供给用户在键盘终端上使用;后者则是提供给用户(主要是程序员)编程时使用。而要学习系统调用,首先要从程序接口入手。
1、 程序接口
程序接口是操作系统专门为用户程序设置的,也是用户程序取得OS服务的唯一途径。程序接口通常由系统调用组成。在每个操作系统中,通常都有几十上百条系统调用,它们的作用各有不同,有的用于进程控制、有的用于存储管理、有的用于文件管理等等。在MS WINDOWS下面进行过WIN32编程的人员应该对windows提供的API函数有一定的印象,这些API函数就是windows操作系统提供给程序员的系统调用接口。而Linux作为一个操作系统,当然有它自己的系统调用。
2、 系统调用
通常,在OS的核心中都设置了一组用于实现各种系统功能的子程序,并将它们提供给程序员调用。程序员在需要OS提供某种服务的时候,便可以调用一条系统调用命令,去实现希望的功能,这就是系统调用。各个不同的操作系统有各自的系统调用,正如前文所讲的windows API,便是windows的系统调用,linux的系统调用与之不同的是linux由于内核代码完全公开,所以可以细致的分析出其系统调用的机制。
1、 系统调用和普通过程的区别
(1)运行于不同的系统状态
如前所述,用户程序可以通过系统调用进入系统空间,而普通过程则只能在用户空间当中运行。
(2)通过软中断切换
由于用户程序使用系统调用后要进入系统空间,所以需要调用一个软中断;而普通过程在被调用时没有这个过程。
2、 系统调用的类型
系统调用的作用与它所在的操作系统有密切关系,根据操作系统的性质不同,它们所提供的系统调用会有一定的差异,不过对于普通操作系统而言,应该具有下面几类系统调用。
(1)进程控制类型。
(2)文件操纵类型。
(3)进程通信类型。
(4)数据管理类型。
3、 系统调用的实现机制。
由于操作系统的不同,其系统调用的实现方式可能不同,然而实现机制应该是大致相同的,一般包含下面几个步骤:
(1)设置系统调用号
在系统当中,往往设置多条系统调用命令,并赋予每条系统调用命令一个唯一的系统调用号。
(2)处理系统调用
操作系统当中有个一张系统调用入口表。表中的每个表目都对应一条系统调用命令,它包含有该系统调用自带参数的数目、系统调用命令处理程序的入口地址等等。操作系统内核便是根据所输入的系统调用号在该表中查找到到相应的系统调用,进而转入它的入口地址去执行它。
【实现步骤】
1、拷贝源码
拷贝linux-3.2.tar.bz2 到虚拟机/usr/src目录下
2、解压源码
#tar xvjf linux-3.2.tar.bz2
3、添加源代码
#vi linux-3.2/kernel/sunplus.c
新建一个文件sunplus.c。在此文件中添加系统调用函数源代码,该函数的名称应该是新的系统调用名称前面加上sys_标志。假设新加的系统调用为sunpluscall,则该函数应该这样写
#include
asmlinkage long sys_sunpluscall(void)
{
printk(KERN_EMERG "this is sunpluscall KERN_EMERG\n");
printk(KERN_ALERT "this is sunpluscall KERN_ALERT\n");
printk(KERN_CRIT "this is sunpluscall KERN_CRIT\n");
printk(KERN_ERR "this is sunpluscall KERN_ERR\n");
printk(KERN_WARNING "this is sunpluscall KERN_WARNING\n");
printk(KERN_NOTICE "this is sunpluscall KERN_NOTICE\n");
printk(KERN_INFO "this is sunpluscall KERN_INFO\n");
printk(KERN_DEBUG "this is sunpluscall KERN_DEBUG\n");
return 0;
}
4、修改Makefile(编译内核时编译源代码)
把sunplus.c添加到kernel目录下的Makefile中,使其在make编译内核的时候能编译到内核中。
#vi linux-3.2/kernel/Makefile
在14行 obj-y += groups.o 下面插入一行
obj-y += sunplus.o
如图:
5、链接新的系统调用
添加新的系统调用之后,下一个任务是让LINUX内核的其余部分知道该程序的存在。增加新函数的链接,需要进行下面的操作。
(1)为新的系统调用添加系统调用号
系统调用号的定义格式如下:
#define __NR_name NNN
其中,name用系统调用名称代替,而NNN是该系统调用对应的号码。应该将新的系统调用名称加到清单的最后,并给它分配已经用到的系统调用号后面的一个号码。
LINUX内核自身用到的系统调用号已经用到348了。而如果读者还要自行增加系统调用,就必须从349开始。
#vi linux-3.2/arch/x86/include/asm/unistd_32.h
在文件356行#define __NR_process_vm_writev 348 下面插入一行
#define __NR_sunpluscall 349
把 #define NR_syscalls 349
修改成
#define NR_syscalls 350
NR_syscalls 这个宏表示系统调用的总个数。
如图:
(2)修改系统调用的指针列表
vi linux-3.2/arch/x86/kernel/syscall_table_32.S
在文件350行.long sys_process_vm_writev 下面添加一行
.long sys_sunpluscall
如图:
(3)vi linux-3.2/arch/x86/ia32/ia32entry.S
在文件854行.quad compat_sys_process_vm_eritev 下面添加一行
.quad sys_sunpluscall
如图:
6、重新编译、安装
(1)清除残留的.config和.o
在linux-3.2 目录下 输入命令
#make mrproper
该命令的功能在于清除当前目录下残留的.config和.o文件,这些文件一般是以前编译时未清理而残留的。而对于第一次编译的代码来说,不存在这些残留文件,所以可以略过此步,但是如果该源代码以前被编译过,那么强烈建议执行此命令,否则后面可能会出现未知的问题。
(2)配置编译选项
作为操作系统的内核,其内容和功能必然非常繁杂,包括处理器调度,内存管理,文件系统管理,进程通讯以及设备管理等等,而对于不同的硬件,其配置选项也不相同,所以在编译源代码之前必须设置编译选项。
配置命令有 make menuconfig 或者make xconfig。我使用的是make menuconfig,但是前提条件是要装ncurses。
1)输入命令:
sudo apt-get install libncurses5-dev
更新安装ncurses。 注意一定要联网。
2)输入命令 :
make menuconfig
注意把vmware放大到全屏终端放大到全屏,因为输入make menuconfig 命令后会弹出一个窗口出来,如果不放大会出错,弹不出窗口。
选择exit,保存默认配置
(3)编译内核
1)清除以前编译生成的 .o 等文件。输入命令:
make clean
2)编译内核 此步大约需要 一个半到两个小时(看机器的性能)。 输入命令:
make bzImage
3)编译modules 输入命令:
make modules
4)安装modules 就是把刚才编译生产的modules拷到系统文件夹下,以供新内核调用。 输入命令:
make modules_install
5)建立要载入ramdisk的映像文件
如果linux系统安装在scsi磁盘上,这步是必须的,否则可以跳过。
切换至/usr/src目录
输入命令:
mkinitramfs -o /boot/initrd.img-3.2.0 3.2.0
(4)安装内核
输入命令:
make install
此时系统会把linux内核的镜像文件还有System.map考入到/boot下,然后会自动生成引导菜单。
7、配置grub
配置grub引导程序
ubuntu系统中grub的默认等待时间为0,要想进入grub菜单,就要修改下等待时间。
修改系统文件/etc/default/grub
将GRUB_HIDDEN_TIMEOUT=0改为GRUB_HIDDEN_TIMEOUT=10
如图:
修改系统文件/etc/default/grub后需用update-grub命令自动生成启动的选项。
执行
sudo update-grub
要想系统重启后的grub引导界面有我们安装好的内核选项我们还需修改/boot/grub/grub.cfg 文件。
屏蔽 124行 的 // submenu "Previous Linux versions" {
在125行 加入一个 {
如图:
8、重启ubuntu
重启后按 “shift”键, 选择 Ubuntu , Linux 3.2.0 选项
如图:
9、测试
(1)编写测试代码
调用系统调用的方式是使用_syscall宏。2.6.18版本之前的内核,在include/asm-i386/unistd.h文件中定义有7个_syscall宏,分别是:
1 _syscall0(type,name)
2 _syscall1(type,name,type1,arg1)
3 _syscall2(type,name,type1,arg1,type2,arg2)
4 _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
5 _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
6 _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)
7 _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)
但是自2.6.19版本开始,_syscall宏被废除,我们需要使用syscall函数,通过指定系统调用号和一组参数来调用系统调用。
syscall函数原型为:
int syscall(int number, ...);
其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。
#include <stdio.h>
#include <stdlib.h>
#include
#include "../linux-3.2/arch/x86/include/asm/unistd_32.h"
int main(int argc, char *argv[])
{
syscall(349);
return 0;
}
(2)测试
测试代码写完后编译
gcc test.c -o test
注意 系统调用里的打印printk是有优先级的,在使用printk时可指定printk的优先级,只有printk的优先级大于终端时,终端上才会显示出printk打印出的内容。
打印方法1:
切换至字符模式
打印方法2:
使用dmesg函数
Ubuntu切换到字符模式下的方法:
按 ctl + alt + F1 (F1~F6)
切换回图形界面的方法
按 alt + F7