我们都知道系统功能调用是Unix/Linux操作系统向用户程序提供支持的接口,通过这些接口应用程序向操作系统请求服务,控制转向操作系统,而操作系统在完成服务后,将控制和结果返回给用户程序。

系统调用的主要目的是使得用户可以使用操作系统提供的有关设备管理、输入/输出系统、文件系统和进程控制、通信以及存储管理等方面的功能,而不必了解系统程序的内部结构和有关硬件细节,从而起到减轻用户负担和保护系统以及提高资源利用率的作用。

在进行系统功能调用时会由用户态(也称目态)转到核态(也称管态)。昨天我的一信安的朋友还问过我管态和目态的区别,我想来想去也就是这里有大的区别了,在管态下能使用系统的所有资源,调用系统的特权函数,而目态是不行的。在执行系统功能调用时就必须是在管态下执行。

而如果我需要给我的linux系统增加一个系统功能调用的话,就必须弄清楚系统是如何调用那些功能函数的,又是如何由目态变为管态的。在linux下可以通过中断来进入管态,这类中断称为访管中断。

内核中系统调用的过程

在Linux系统中,系统调用是作为一种异常类型实现的

,它将执行相应的机器代码指令来产生异常信号。产生中断或异常的重要效果是系统自动将用户态切换为核心态来对它进行处理。

用户态的程序只有通过门(gate)陷入(trap)到系统内核中去(执行int指令),才能执行一些具有特权的内核函数。系统调用完成后,系统执行另一组特征指令(iret指令)将系统返回到用户态,控制权返回给进程。

Linux用来实现系统调用异常的实际指令是:

int $0x80

这一指令使用中断/异常向量号128(即16进制的80)将控制权转移给内核(进行模式切换)。

为达到在使用系统调用时不必用机器指令编程,在标准的C语言库中为每一系统调用提供了一段短的子程序,完成机器代码的编程工作。

事实上,机器代码段非常简短。它所要做的工作只是将送给系统调用的参数加载到CPU寄存器中,接着执行int

$0x80指令。然后运行系统调用。

系统调用的返回值将送入CPU的一个寄存器中,标准的库子程序取得这一返回值,并将它送回用户程序。我们可以看到其中有一些宏定义,我们可以看看这些宏的定义

(arch/i386/kernel/ entry.S).

………

#define SAVE_ALL \

cld; \

pushl %es; \

pushl %ds; \

pushl �x; \

pushl �p; \

pushl �i; \

pushl %esi; \

pushl �x; \

pushl �x; \

pushl �x; \

movl $(__USER_DS),�x; \

movl �x,%ds; \

movl �x,%es;

我们可以看到SAVE_ALL主要是保存寄存器信息,即现场保留。其中, movl $(__USER_DS),

�x;从这一句开始是重新填充DS,ES段。#define RESTORE_INT_REGS \

popl �x; \

popl �x; \

popl �x; \

popl %esi; \

popl �i; \

popl �p; \

popl �x

#define RESTORE_REGS \

RESTORE_INT_REGS; \

1: popl %ds; \

2: popl %es; \

.section .fixup,"ax"; \

3: movl $0,(%esp); \

jmp 1b; \

4: movl $0,(%esp); \

jmp 2b; \

.previous; \

.section __ex_table,"a";\

.align 4; \

.long 1b,3b; \

.long 2b,4b; \

.previous ENTRY(ret_from_fork)

pushl �x

call schedule_tail

GET_THREAD_INFO(�p)

popl �x

jmp syscall_exit

这里主要完成现场恢复并返回。

ENTRY(system_call)

pushl �x # save orig_eax

SAVE_ALL

GET_THREAD_INFO(�p)

# system call tracing in operation

testw

$(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SECCOMP),TI_flags(�p)

jnz syscall_trace_entry

cmpl $(nr_syscalls), �x

jae syscall_badsys

syscall_call:

call *sys_call_table(,�x,4)

movl �x,EAX(%esp) # store the return value

syscall_exit:

cli # make sure we don't miss an interrupt

# setting need_resched or sigpending

# between sampling and the iret

movl TI_flags(�p), �x

testw $_TIF_ALLWORK_MASK, %cx # current->work

jne syscall_exit_work

restore_all:

movl EFLAGS(%esp), �x # mix EFLAGS, SS and CS

# Warning: OLDSS(%esp) contains the wrong/random values if

we

# are returning to the kernel.

# See comments in process.c:copy_thread() for details.

movb OLDSS(%esp), %ah

movb CS(%esp), %al

andl $(VM_MASK | (4 << 8) | 3), �x

cmpl $((4 << 8) | 3), �x

je ldt_ss # returning to user-space with LDT SS

restore_nocheck:

RESTORE_REGS

addl $4, %esp

1: iret

这一段中,主要是完成调用。eax放置的是系统调用号 ,因为eax有可能被使用,所以先保存其值。call

*sys_call_table(,�x,4) 这一句是计算调用的入口。

其中,sys_call_table是LINUX的系统调用表,它存在目录arch/i386/kernel/

sys_call_table.S 下。

.data

ENTRY(sys_call_table)

.long sys_restart_syscall

.long sys_exit

.long sys_fork

.long sys_read

.long sys_write

.long sys_open

……

……

.long sys_mq_timedreceive

.long sys_mq_notify

.long sys_mq_getsetattr

.long sys_ni_syscall

.long sys_waitid

.long sys_ni_syscall

.long sys_add_key

.long sys_request_key

.long sys_keyctl

.代表当前地址,sys_call_table代表数组首地址。这个表依次保存所有系统调用的函数指针,以方便总的系统调用处理函数(system_call)进行索引。

调用具体的实现在kernel/sys.c中。

asmlinkage long

sys_getuid16(void)

{

return hig2lowuid(current_uid);

} 刚才我们提到,这一指令使用中断/异常向量号128(即16进制的80)将控制权转移给内核,那么中断向量是怎么形成的。它的定义在(arch/i386/kernel/traps.c)中。

void __init trap_init(void)

{

……

set_trap_gate(0,÷_error);

set_trap_gate(1,&debug);

set_intr_gate(2,&nmi);

set_system_gate(3,&int3);

set_system_gate(4,&overflow);

set_system_gate(5,&bounds);

set_trap_gate(6,&invalid_op);

set_trap_gate(7,&device_not_available);

set_trap_gate(8,&double_fault);

set_trap_gate(9,&coprocessor_segment_overrun);

set_trap_gate(10,&invalid_TSS);

set_trap_gate(11,&segment_not_present);

set_trap_gate(12,&stack_segment);

set_trap_gate(13,&general_protection);

set_intr_gate(14,&page_fault);

set_trap_gate(15,&spurious_interrupt_bug);

set_trap_gate(16,&coprocessor_error);

set_trap_gate(17,&alignment_check);

set_trap_gate(18,&machine_check);

set_trap_gate(19,&simd_coprocessor_error);

set_system_gate(,&system_call);

……

}

上一句就是设置system_call 的值。SYSCALL_VECTOR的值就是0X80 .

那么概括起来,系统调用的过程大致如下:

(1) 系统调用初始化

在traps.c中,系统在初始化程序trap_init()中,通过调用

set_system_gate(0x80,*system_call)

完成中断描述表的填充。这样当每次用户执行指令int 0x80时,系统能把控制转移到entry.S中的函数中去。

(2) 系统调用执行

system_call会根据用户传进来系统调用号,在系统调用表

system_call中寻找到相应偏移地址的内核处理函数,进行相应的处理。当然在这个过程之前,要保存环境(SAVE_ALL)。

(3) 系统调用的返回

系统调用处理完毕后,通过sys_call_exit返回。返回之前,程序会检查一些变量,相应地返回。不一定是返回到用户进程。真正返回到用户空间时,要恢复环境(restore_all)。

用户程序中系统调用的过程

在前面提到system_call会根据用户传进来系统调用号,在系统调用表

system_call中寻找到相应偏移地址的内核处理函数,进行相应的处理。

那么系统调用号怎么产生,在include/asm-i386/unistd.h 中可以看到系统调用号的定义。

#define __NR_restart_syscall 0

#define __NR_exit 1

#define __NR_fork 2

#define __NR_read 3

#define __NR_write 4

#define __NR_open 5

#define __NR_close 6

#define __NR_waitpid 7

#define __NR_creat 8

#define __NR_link 9

……

#define __NR_mq_open 277

#define __NR_mq_unlink (__NR_mq_open+1)

#define __NR_mq_timedsend (__NR_mq_open+2)

#define __NR_mq_timedreceive (__NR_mq_open+3)

#define __NR_mq_notify (__NR_mq_open+4)

#define __NR_mq_getsetattr (__NR_mq_open+5)

#define __NR_sys_kexec_load 283

#define __NR_waitid 284

#define __NR_add_key 286

#define __NR_request_key 287

#define __NR_keyctl 288

#define NR_syscalls 289

此处的代码是从2.6.11中的代码,其中系统调用号已到了288,并且与前面system_call中的相对应。每一个系统调用号前都是相应函数名加了__NR_。

内核跟用户程序的交互,其实有标准C库作为它们之间的桥梁。标准C库把用户希望传递的参数装载到CPU的寄存器中,然后触发0X80中断。

当从系统调用返回的时候(sys_call_exit),标准C库又接过控制权,处理返回值。

对于__NR_,标准C库会作相应处理。转换成相应函数。

对于系统函数的调用,有几个通用的宏在include/asm-i386/unistd.h中定义。

#define __syscall_return(type, res) \

do { \

if ((unsigned long)(res) >= (unsigned long)(-(128 + 1))) {

\

errno = -(res); \

res = -1; \

} \

return (type) (res); \

} while (0)

#else

# define __syscall_return(type, res) return (type) (res)

#endif

#define _syscall0(type,name) \

type name(void) \

{ \

long __res; \

__asm__ volatile ("int $0x80" \

: "=a" (__res) \

: "0" (__NR_##name)); \

__syscall_return(type,__res); \

}

这是无参函数调用的形式。

#define _syscall1(type,name,type1,arg1) \

type name(type1 arg1) \

{ \

long __res; \

__asm__ volatile ("int $0x80" \

: "=a" (__res) \

: "0" (__NR_##name),"b" ((long)(arg1))); \

__syscall_return(type,__res); \

}

这是含一个参数的调用形式,

……

标准C库会把我们的调用如pause()转换成相应的形式。linux培训

pause()

int pause(void)

{

long __res;

__asm__ volatile(“int $0x80”

:”=a”(__res)

:””(__NR_pause));

__syscall_return(int,__res);

}

进入内核调用过程。

linux内核编译系统调用,linux编译内核及添加系统调用相关推荐

  1. Linux-0.11内核学习-添加系统调用

    1.参考资料 赵炯博士的网站oldlinux Linux内核完全注释 Linux0.11 源码 2.概要 操作系统作为软件应用层和底层硬件之间的部分,向下提供服务,向上提供接口.系统调用便是操作系统向 ...

  2. 【Linux】Linux添加系统调用以及内核编译过程

    在想要替换原有系统内核或者需要在原来的系统中添加一些系统调用的时候就会涉及到Linux内核的编译.但是内核编译虽然步骤简单,但是需要注意的东西还是太多了.首先一点就是由于Linux的开源性导致的版本问 ...

  3. Linux 系统调用(二)——使用内核模块添加系统调用(无需编译内核)

    本文将介绍Linux使用内核模块添加系统调用的方法(无需编译内核),思路就是修改映射在内存中的系统调用表,把一个空闲的系统调用表项指向自己写的模块中的函数,如果是已使用的表项,甚至可以实现系统调用劫持 ...

  4. linux内核编译及添加系统调用(hdu)_浅谈关于Linux内核write系统调用操作的原子性

    Linux系统的write调用到底是不是原子的.网上能搜出一大堆文章,基本上要么是翻译一些文献,要么就是胡扯,本文中我来结合实例来试着做一个稍微好一点的回答. 先摆出结论吧.结论包含两点,即write ...

  5. 操作系统实验(linux内核编译,添加系统调用,windows进程创建,脚本程序编写)

    <操作系统原理>实验报告 一.实验目的 (1)理解操作系统生成的概念和过程; (2)理解操作系统两类用户界面(操作界面,系统调用)概念; 二.实验内容 (1)在Unbantu或Fedora ...

  6. 杭电操作系统实验一 --- Linux内核编译及添加系统调用(arm架构华为云)

    实验要求  掌握Linux 内核的编译与安装 掌握Linux 系统调用基本概念 设计和添加linux系统调用 (1)修改或返回指定进程的优先级(nice值和prio值)(详见教材P328)提示:可能参 ...

  7. 杭电操作系统实验一----Linux内核编译及添加系统调用(完整实验报告)

    一 题目介绍 Linux是开源操作系统.在系统中根据需要添加新的系统调用是修改内核的一种常用手段,通过本次实验,我们可以理解Linux系统处理系统调用的流程以及增加系统调用的方法.Linux系统提供了 ...

  8. linux 2.6 添加系统调用,在Fedora 13里编译内核(linux-2.6.36)+添加系统调用

    在Fedora 13里编译内核(linux-2.6.36)+添加系统调用 首先说明:我系统本来内核是2.6.33版本,我编译的是2.6.36版本 第一步:下载内核源代码.我下载的是linux-2.6. ...

  9. Linux内核2.6.34.14添加系统调用及编译方法(CentOS-6.4-x86_64)

    <?xml version="1.0" encoding="UTF-8"?> //我添加系统调用步骤,仅供参考,尤其是系统调用的实现部分,建议大家自 ...

最新文章

  1. 《Ceph源码分析》——第1章,第5节RADOS
  2. 计算机知识的更新速度,笔记本电脑硬盘如何升级?要容量还是速度?这些知识你需要学会...
  3. oracle乘法运算,oracle实现相乘话语
  4. initialProps被React-Navigation的navigation属性覆盖解决方案
  5. Unity检视面板的继承方法研究
  6. 鸿蒙 电视 安卓,华为鸿蒙2.0来了!打通手机、电视、PC全平台,Mate 40 整装齐发...
  7. Redis在windows下安装过程
  8. 为了让你的网页能在更多的服务器上正常地显示,还是加上“SET NAMES UTF8”吧
  9. 80-10-020-原理-Java NIO-HeapByteBuffer
  10. sybase SET CHAINED OFF与SET CHAINED ON两种事务模式的区别
  11. 使用函数计算打包下载OSS文件
  12. pycharm中的常用快捷键与常用设置
  13. 6.从Paxos到Zookeeper分布式一致性原理与实践---Zookeeper 的典型应用场景
  14. SSM小区停车场管理系统
  15. android模拟器快捷键,Android模拟器快捷键大全
  16. mysql pxc介绍_MySQL高可用之PXC简介
  17. TILDE: A Temporally Invariant Learned DEtector学习笔记
  18. 3D场景建模学习必备的基础知识
  19. Promise晋级—完全吃透
  20. 为何需要物联网设备管理平台

热门文章

  1. 关于 pd.Series 排序后用索引和行号取值要注意的问题
  2. Flink Checkpoint/Savepoint
  3. 中文计数法亿兆京垓秭穰沟涧正载
  4. 移动端跨平台开发方案解析
  5. 为什么我还在用Windows?
  6. 每七本 你的生命有什么可能
  7. Resumable 文件分块上传
  8. mirai源代码c语言,Mirai源码解析
  9. java如何输出大小不同的字_Eclipse下中英文字体大小不一致,如何解决
  10. 计算机字符格式化集体备课教案,集体备课教案范文