linux系统调用

Linux体系结构

内核空间与用户空间是程序执行的两种不同状态,通过系统调用和硬件中断能够完成从用户空间到内核空间的转移。如下图所示:

linux 体系结构图


从上图得知,Linux由用户空间和内核空间

一般情况下,用户进程是不能访问内核的。它既不能访问内核所在的内存空间,也不能调用内核中的函数。Linux内核中设置了一组用于实现各种系统功能的子程序,用户可以通过调用他们访问linux内核的数据和函数,这些系统调用接口(SCI)称为系统调用。


系统调用和普通函数的区别:

系统调用和普通的函数调用非常相似,区别仅仅在于,系统调用由操作系统内核实现,运行于内核态;而普通的函数调用由函数库或用户自己提供,运行于用户态。


系统调用数:

在2.6.32 版内核中,共有系统调用365个,可在arch/arm/include/asm/unistd.h中找到它们。

/* This file contains the system call numbers*/

#define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0)
#define __NR_exit (__NR_SYSCALL_BASE+ 1)
#define __NR_fork (__NR_SYSCALL_BASE+ 2)
......

#define __NR_preadv (__NR_SYSCALL_BASE+361)
#define __NR_pwritev (__NR_SYSCALL_BASE+362)
#define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363)
#define __NR_perf_event_open (__NR_SYSCALL_BASE+364)


系统调用的功能:

主要分为3大类:

(1)进程控制类

fork 创建一个子进程

clone  按照指定条件创建子进程

execve 运行可执行文件

...

(2)文件控制操作

fcntl 文件控制

open 打开文件

read 读文件

...

(3)系统控制

ioctl I/O总控制函数

reboot重新启动

—sysctl读写系统参数

...


使用系统调用函数举例:

下面通过time函数系统调用实现从格林尼治时间1970年1月1日0:00开始到现在的秒数。

#include<time.h>
main()
{
time_t t_time;
t_time=time((time_t *)0); /*调用time系统调用*/
printf("The time is %ld\n",t_time);
}


系统调用工作原理:

一般情况下,用户进程是不能访问内核的。它既不能访问内核所在的内存空间,也不能调用内核中的函数。系
统调用是一个例外。其原理是(1)进程先用适当的值填充寄存器,(2)然后调用一个特殊的指令,(3)这个指令会让用户程序跳转到一个事先定义好的内核中的一个位置。(4)
进程可以跳转到的固定的内核位置。这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。接着,就调用函数,等返回后,做一些系统检查,最后返回到进程。


工作原理概述:

(1)适当的值

所有适当的值我们都可以在include/asm/unistd.h中找到,在这个文件中为每一个系统调用规定了唯一的编号,叫做系统调用号。

#define __NR_utimensat (__NR_SYSCALL_BASE+348)
#define __NR_signalfd (__NR_SYSCALL_BASE+349)
#define __NR_timerfd_create (__NR_SYSCALL_BASE+350)
#define __NR_eventfd (__NR_SYSCALL_BASE+351)
#define __NR_fallocate (__NR_SYSCALL_BASE+352)

这里面每一个宏就是一个系统调用号

(2)特殊的指令

在Intel CPU中,这个指令由中断0x80实现

在ARM中,这个指令是SWI(softwhere interrupt:软中断指令),现在重新命名为SVC

(3)固定的位置

每个CPU固定的位置是不一样的,在ARM体系中这个固定的内核位置是ENTRY(vector_swi)(在arch\sh\kernel\entry-common.S),也就是PC指针会跳转到这个位置

(4)相应的函数

内核根据应用程序传递来的系统调用号,从系统调用表sys_call_table找到相应的内核函数

CALL(sys_restart_syscall)

CALL(sys_exit)

CALL(sys_fork_wrapper)


实例:

工作原理(应用):下面是一个从用户open调用到找到内核中具体的系统调用函数入口地址的大体流程

#define __syscall(name) "swi\t" __NR_##name "\n\t“
int open( const char * pathname, int flags)
{
。。。。。。
__syscall(open);
。。。。。。
}
转化为
int open( const char * pathname, int flags)
{
。。。。。。
swi\t __NR_open  //#define __NR_open (__NR_SYSCALL_BASE+  5)
。。。。。。
}

//内核入口

/* arch/arm/kernel/entry-common.S */
ENTRY(vector_swi)
…… …… …… ……
adr tbl, sys_call_table @ load syscall table pointer
…… …… …… ……
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
…… …… …… ……
ENTRY(sys_call_table)


#include "calls.S"

/* arch/arm/kernel/calls.S */
/* 0 */ CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)
CALL(sys_read)
CALL(sys_write)
/* 5 */ CALL(sys_open)
………………………………………………………………
CALL(sys_dup3)
CALL(sys_pipe2)
/* 360 */CALL(sys_inotify_init1)




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。