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)