很多朋友对于Linux 系统调用的来龙去脉(第2 部分)和不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
系统调用向应用程序提供调用请求接口。软中断指令在调用请求中执行。应用程序使用调用请求后,处理器会产生中断,执行中断服务,中断服务会根据调用号执行具体的功能。功能。
2.调用请求
linux系统调用的第一部分是调用请求。调用请求作为系统调用向应用程序提供的接口。 linux系统调用的三部分中,应用程序只有调用请求有使用权限(其他两部分应用程序无权访问),调用请求需要完成以下两个基本功能:
2.1接口函数
Linux系统调用有四种调用请求方式: 1.使用glibc库函数。 2. 使用系统调用函数。 3.通过Linux系统调用宏。 4.使用软中断来陷阱。
对于以上四种方法,每种方法底层都会用到触发软中断指令。
1.使用glibc库函数
glibc是GNU发布的开源标准C库。 glibc为程序员提供了丰富的API(应用程序编程接口)。除了字符串处理、数学运算等用户态服务外,封装操作系统提供的系统服务也很重要。即系统调用的封装(本质上还是执行的系统调用)。
系统调用与glibc提供的API的对应关系如下: 场景一:每个系统调用对应一个glibc库函数。例如系统提供的打开文件系统调用sys_open就对应了glibc中的open函数。
场景2:单个glibc库函数可能调用多个系统调用。例如glibc提供的printf函数会调用sys_open、sys_mmap、sys_write、sys_close等系统调用。
场景3:多个glibc库函数可能只对应同一个系统调用。比如glibc下实现的malloc、calloc、free等函数都是用来分配和释放内存的,都使用了内核的sys_brk系统调用。
例如,应用程序使用glibc提供的chmod函数来更改文件的属性。示例代码如下:
#includestdio.hint main(){ int rc=chmod(‘./weiwei’,0666); if(rc==-1) perror(‘chmod 失败\n’); else printf(‘chmod 成功\n’) ; return 0;} chmod函数用于改变文件weiwei的属性。编译器获得chmod.o可执行文件。运行chmod.o后,观察weiwei文件的属性。结果如下:
2. 使用syscall 使用glibc 有很多好处。 glibc封装了操作系统提供的系统服务。程序员不需要了解细节,例如chmod 系统调用号。程序员只需要知道glibc提供的API的原型即可。其次,使用glibc的方法具有更好的可移植性,可以方便地移植到其他平台上。
一切都有两个方面。如果glibc没有封装某个内核提供的系统调用,我们将无法使用glibc的方法来调用该系统调用。假设我们通过编译内核来添加系统调用。此时glibc无法为我们自己的新系统调用提供封装API。
这种情况下,可以使用glibc提供的syscall库函数直接调用。 syscall是一个库函数,通过特定的子函数号和特定的参数来调用汇编语言接口。该函数定义在unistd.h头文件中,函数原型如下:
long int syscall (long int sysno,)sysno:是系统调用号。每个系统调用都由唯一的系统调用号标识。变长参数,是系统调用携带的参数。不同的系统调用可以携带0到5的参数。 返回值:该函数的返回值是特定系统调用的返回值。如果系统调用失败,则返回-1。
3、通过_syscall系统调用宏,Linux内核提供了一组用于实现系统调用接口函数的宏。系统调用实现后,应用程序就可以使用该系统调用了。这组宏是_syscalln(),其中n的范围是0到6。这组宏将设置寄存器并调用软中断指令。 _syscalln()宏如下:
_syscall0()、_syscall1()、_syscall2()、_syscall3()、_syscall4()、_syscall5()、_syscall6()。宏名称字符串“syscall0”中的0 表示无参数,“syscall1”中的1 表示一个参数,“syscall2”中的2 表示两个参数。如果系统调用需要1 个参数,则应使用宏_syscall1()。
用户态程序通过软中断指令int0x80陷入内核态,并通过寄存器传递参数。 eax 传递系统调用号。 ebx、ecx、edx、esi 和edi 按顺序传递参数。当系统调用返回值存储在eax 中。
以调用chmod修改文件属性为例。系统调用是通过内联汇编实现的。示例代码如下:
#include stdio.h #include errno.h #include sys/syscall.h //头文件syscall.h中的SYS_chmod #include sys/types.hint main(){ long rc;无符号短模式=0777; char *file_name=’./weiwei’;/*内联汇编软中断指令*/asm( ‘int $0x80’ : ‘=a’ (rc) : ‘0’ (SYS_chmod), ‘b’ ((long)file_name ), ‘c’ ((长)模式)); if (rc==-1) perror(‘SYS_chmod chmod 失败\n’); else printf(‘SYS_chmod chmod 成功\n’); return 0;}
2.2触发软中断
及以上这4个方法,每个方法底层都会用到触发软中断指令。系统调用的参数通过各个通用寄存器传递,然后执行软中断指令(INT0x80)。处理器响应中断并开始执行中断服务程序。此时,处理器模式转变为特权模式。我们以glibc-2.23中的X86系统为例。下面分别介绍四种方法中如何调用软中断指令。
1.glibc库函数
glibc-2.23\sysdeps\unix\sysv\linux\generic中有chmod函数的定义,代码如下:
#include errno.h#include stddef.h#include fcntl.h#include sys/stat.h#include sys/types.h/* 将FILE 的保护更改为MODE。 */int__chmod(const char *file, mode_t mode){ return INLINE_SYSCALL(fchmodat, 3, AT_FDCWD, file, mode);}weak_alias(__chmod, chmod)INLINE_SYSCALL宏定义在glibc-2.23\glibc-2.23\sysdeps\i386中\sysdep.h,代码如下:
# INLINE_SYSCALL(name, nr, args.) \ ({ \ unsigned int resultvar=INTERNAL_SYSCALL (name, nr, args); \ __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (resultvar, )) \ ? __syscall_error (-INTERNAL_SYSCALL_ERRNO (resultvar, ) Define ) \ : (int) resultvar; })INTERNAL_SYSCALL宏定义在glibc-2.23\glibc-2.23\sysdeps\i386\sysdep.h中,代码如下:
#define INTERNAL_SYSCALL(name, err, nr, args.) \ ({ \ register unsigned int resultvar; \ INTERNAL_SYSCALL_MAIN_##nr (name, err, args); \ (int) resultvar; })INTERNAL_SYSCALL_MAIN_##nr 宏glibc-2.23\glibc-2.23\sysdeps\i386\sysdep.h 中定义了几种形式,如下:
#define INTERNAL_SYSCALL_MAIN_0(名称, err, args.) \ INTERNAL_SYSCALL_MAIN_INLINE(name, err, 0, args)#define INTERNAL_SYSCALL_MAIN_1(name, err, args.) \ INTERNAL_SYSCALL_MAIN_INLINE(name, err, 1, args)#define Internet_Syscall_main_2(name, err, args .)\internal_syscall_main_inline(name, err, args) #定义Internal_Main_3(name, errgs .)\Internet_Syscall_main_inline(name, ERR, 3, ARGS) #定义Internet_Syscall_main_4(name, err, args.) \ INTERNAL_SYSCALL_MAIN_INLINE(name, err, 4, args)#define INTERNAL_SYSCALL_MAIN_5(name, err, args.) \ INTERNAL_SYSCALL_MAIN_INLINE(name, err, 5, args) glibc-2.23中的INTERNAL_SYSCALL_MAIN_INLINE宏在\glibc中定义-2.23\sysdeps\i386\sysdep.h,代码如下:
# 定义INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args.) \ LOADREGS_##nr(args)\ asm volatile (\ ‘int $0x80’\ : ‘=a’ (resultvar)\ : ‘a’ (__NR__# #name) ASMARGS_##nr(args) : ‘memory’, ‘cc’) chmod库函数的定义最终通过int $0x80触发软中断。
2.系统调用函数
glibc-2.23\glibc-2.23\sysdeps\unix\sysv\linux\hppa\syscall.c中的syscall函数定义如下:
long intsyscall (long int __sysno,){ /* FIXME: 为hppa 保留此匹配的INLINE_SYSCALL */va_list args;长整型arg0、arg1、arg2、arg3、arg4、arg5;长整型__sys_res; /* 加载可变参数*/va_start (args, __sysno); arg0=va_arg (args, 长整型); arg1=va_arg (args, 长整型); arg2=va_arg (args, 长整型); arg3=va_arg (args, 长整型); arg4=va_arg (args, 长整型); arg5=va_arg (args, 长整型); va_end(参数); { LOAD_ARGS_6 (arg0, arg1, arg2, arg3, arg4, arg5) 寄存器unsigned long int __res asm(‘r28’); PIC_REG_DEF LOAD_REGS_6 asm 易失性(SAVE_ASM_PIC ‘ble0x100(%%sr2, %%r0)\n’ ‘复制%1, %%r20\n’ LOAD_ASM_PIC : ‘=r’ (__res) : ‘r’ (__sysno) PIC_REG_USE ASM_ARGS_6第:章__sys_res=__res; } if ((无符号长整型) __sys_res=(无符号长整型) -4095) { __set_errno (-__sys_res); __sys_res=-1; } return __sys_res;} 在syscall函数中以嵌入式汇编的形式调用软中断指令。
3、_syscall系统调用宏以_syscall0()为例。它以嵌入式汇编的形式调用软中断指令int0x80。
4. 通过软中断指令进行陷阱。要使用软中断进行陷阱,可以直接使用汇编指令int $0x80 来触发软中断。
3.响应请求
软中断是通过特定指令SWI实现的。当CPU执行SWI指令时,会触发中断并进入中断程序。以X86架构为例,软件中断指令(int0x80)用于在处理器从用户模式转变为特权模式时产生软中断。
应用程序调用系统调用接口函数后,底层会执行软中断指令(int0x80),处理器会从用户态切换到内核态并跳转到0x80中断向量号,对应的中断向量服务程序,并根据传入的呼叫号码执行相应的系统服务函数。中断触发后的响应流程如下:
syscall_call准备SAVE_ALL的主要工作是在调用系统调用服务程序之前将gs、fs、es、ds、eax、ebp等寄存器中的值压入内核堆栈并保存。
cmpl $(nr_syscalls),%eax指令语句用于判断从用户态传入的系统调用号是否大于系统中实现的最大系统调用号,并判断传入的系统调用号是否合法。如果不合法, 程序跳转到syscall_badsys执行相应的错误处理程序。
syscall_call在调用syscall_call时的工作就是调用传入的系统调用号对应的系统调用服务程序。这一步只有一个非常简单的调用指令语句,如下所示:
call *sys_call_table(,%eax,4)call *sys_call_table(,%eax,4)根据eax中传入的系统调用号调用相应的系统调用服务程序。 sys_call_table 是函数指针数组(跳转表)。
sys_call_table跳转表
sys_call_table定义如下:
/* 代码文件路径:/linux-2.6.28.6/arch/x86/kernel/syscall_32.c */#undef __SYSCALL#define __SYSCALL(nr, sym) [nr]=sym,#undef _ASM_X86_UNISTD_64_Htypedef void (*sys_call_ptr_t)( void);extern void sys_ni_syscall(void);const sys_call_ptr_t sys_call_table[__NR_syscall_max+1]={/**闻起来像编译器错误——它不起作用*当删除下面的内容时。*/[0 . __NR_syscall_max]=sys_ni_syscall,#include asm/unistd_64.h};sys_call_table是一个函数指针类型的数组。数组的长度为系统包含的所有系统调用的个数__NR_syscall_max + 1, [0 . __NR_syscall_max]=sys_ni_syscall code 完成的是数组元素的初始化。 #include asm/unistd_64.h 直接扩展数组中头文件的内容。
asm/unistd_64.h文件内容如下:
#ifndef _ASM_X86_UNISTD_64_H#define _ASM_X86_UNISTD_64_H#ifndef __SYSCALL#define __SYSCALL(a, b)#endif/* 每个缓存行至少8 个系统调用*/#define __NR_read0__SYSCALL(__NR_read, sys_read)#define __NR_write1__SYSCALL(__NR_ write , sys_write)#define __NR_open2__SYSCALL( __NR_open , sys_open)#define __NR_close3__SYSCALL(__NR_close, sys_close)#define __NR_stat4__SYSCALL(__NR_stat, sys_newstat)#define __NR_fstat5__SYSCALL(__NR_fstat, sys_newfstat)#define __NR_lstat6__SYSCALL(__NR_lstat, sys_newlstat)#define __NR _poll7__SYSCALL(__NR_poll, sys_poll) .省略代码.sys_call_table扩展为:
__visible const sys_call_ptr_t sys_call_table[__NR_syscall_max+1]={ [0 ]=sys_read, [1]=sys_write [2]=sys_open,[3]=sys_close,代码省略.};sys_call_table[ 0]是执行sys_read函数。使用跳转表后程序的执行状态如下:
4.功能实现
系统调用号与系统调用服务程序一一对应。一个系统调用对应一个系统调用服务程序。 90号系统调用对应的系统调用服务程序为sys_chmod,如下所示:
#define __NR_chmod90__SYSCALL(__NR_chmod, sys_chmod) sys_chmod 函数定义如下:
linux-2.6.28.6\linux-2.6.28.6\fs\open.c SYSCALL_DEFINE2(chmod, const char __user *, filename, mode_t, mode){return sys_fchmodat(AT_FDCWD, filename, mode);} 为什么不sys_chmod() as那函数名呢? Linux系统中的系统调用服务函数都是使用SYSCALL_DEFINEx(0,1,2,3,4,5,6)宏来实现的,这使得用户态和内核态的两个函数“看起来一样”。
SYSCALL_DEFINEx的定义如下,
/* 代码文件路径:/linux-3.18.6/include/linux/syscalls.h */#define SYSCALL_METADATA(sname, nb,)#define SYSCALL_DEFINE0(sname) \ SYSCALL_METADATA(_##sname, 0) ; \ asmlinkage long sys_##sname(void)#define SYSCALL_DEFINE1(name,) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)#define SYSCALL_DEFINE2(name,) SYSCALL_DEFINEx(2, _##name , __VA_ARGS__)#define SYSCALL_DEFINE3(name,) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)#define SYSCALL_DEFINE4(name,) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)#define SYSCALL_DEFINE5(name ,) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)#define SYSCALL_DEFINE6(name,) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)#define SYSCALL_DEFINEx(x, sname,) \ SYSCALL_METADATA (sname, x, __VA_ARGS__) \ __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)#define __PROTECT(.) asmlinkage_protect(__VA_ARGS__)#define __SYSCALL_DEFINEx(x, name,) \ asmlinkage long sys##name(__MAP ( x,__SC_DECL,__VA_ARGS__)) \ __attribute__((alias(__stringify(SyS##name)))); \所以根据上面的定义,宏SYSCALL_DEFINE2(chmod, const char __user *, filename, mode_t, mode)展开后的结果是:
asmlinkage long sys_chmod(const char __user *filename, mode_t mode){return sys_fchmodat(AT_FDCWD, filename, mode);}
5.自定义一个系统调用
通常现有的系统调用可以满足我们的大部分需求,但在极少数情况下,我们需要添加一个额外的自定义系统调用。接下来让我们学习如何添加自定义系统调用。比如我们自定义一个名为weiweicall的系统调用(这个系统调用的参数为0)。
一旦系统调用的名称确定了,系统调用中的几个相关的名称也就确定了。调用接口
函数:weiweicall系统调用的编号名字:__NR_weiweicall内核中系统调用的实现程序的名字:sys_weiweicall
5.1接口函数在前面我们描述了4种系统调用的接口,我们可以使用linux系统调用宏_syscall0来定义一个可以产生软中断的函数:
#include <linux/unistd.h>_syscall0(int,weiweicall) /* 注意这里没有分号*/int main(){weiweicall();}定义了weiweicall用户系统调用函数,接下来我们定义系统调用号。
5.2添加系统调用号
我们要做的就是在文件asm/unistd_64.h文中添加自定义的系统调用号__NR_weiweicall,代码如下:
linux-2.6.28.6\arch\x86\include\asm\unistd_64.h#define __NR_eventfd2290__SYSCALL(__NR_eventfd2, sys_eventfd2)#define __NR_epoll_create1291__SYSCALL(__NR_epoll_create1, sys_epoll_create1)#define __NR_dup3292__SYSCALL(__NR_dup3, sys_dup3)#define __NR_pipe2293__SYSCALL(__NR_pipe2, sys_pipe2)#define __NR_inotify_init1294__SYSCALL(__NR_inotify_init1, sys_inotify_init1)//增加自定义系统调用#define __NR_weiweicall295__SYSCALL(__NR_weiweicall, sys_weiweicall)添加系统调用号之后,系统才能根据这个号作为索引,去找syscall_table中的相应表项。目前系统已经能够正确地找到并且调用sys_mysyscall。剩下事情的就只剩下sys_mysyscall内核函数的实现。
5.3sys_mysyscall 的实现
我们自定义的程序添加在内核代码中,如kernel/sys.c 里面,我们没有在kernel 目录下另外添加自己的一个文件,这样做的不用修改Makefile,可以直接编译。我们可以用宏实现功能函数,也可以直接定义实现功能函数:
//直接定义sys_xxx函数asmlinkage int sys_weiweicall(void){printk( “weiwei system call!”);return 1;}//使用SYSCALL_DEFINE定义函数SYSCALL_DEFINE0(weiweicall){printk( “weiwei system call!”);return 1;}编译内核后,应用程序就可以使用weiweicall系统调用。
#include <linux/unistd.h>_syscall0(int,weiweicall) /* 注意这里没有分号*/int main(){weiweicall();}
6.系统调用分类
系统调⽤按照功能逻辑⼤致可分为“进程控制”、“⽂件系统控制”、“系统控制”、“存管管理”、“⽹络管理”、“socket控制”、“⽤户管理”、“进程间通信”。
进程控制类系统调用
fork,clone,execve,exit,_exit,getdtablesize,getpgid,setpgid,getpgrp,setpgrp,getpid,getppid,getpriority,setpriority,modify_ldt,nanosleep,nice,pause,personality,prctl,ptrace,sched_get_priority_max,sched_get_priority_min,sched_getparam,sched_getscheduler,sched_rr_get_interval,sched_setparam,sched_setscheduler,sched_yield,vfork,wait,wait3,waitpid,wait4,capget,capset,getsid,setsid
文件操作类系统调用
fcntl,open,creat,close,read,write,readv,writev,pread,pwrite,lseek,_llseek,dup,dup2,flock,polltruncate,ftruncate,umask,fsync
文件系统操作类系统调用
access,chdir,fchdir,chmod,fchmod,chown,fchown,lchown,chroot,stat,lstat,fstat,statfs,fstatfs,readdir,getdents,mkdir,mknod,rmdir,rename,link,symlink,unlink,readlink,mount,umount,ustat,utime,utimes,quotactl
系统控制类系统调用
ioctl,_sysctl,acct,getrlimit,setrlimit,getrusage,uselib,ioperm,iopl,outb,reboot,swapon,swapoff,bdflush,sysfs,sysinfo,adjtimex,alarm,getitimer,setitimer,gettimeofday,settimeofday,stime,time,times,uname,vhangup,nfsservctl,vm86,create_module,delete_module,init_modulequery_module,get_kernel_syms
内存管理类系统调用
brk,sbrk,mlock,munlock,mlockall,munlockall,mmap,munmap,mremap,msync,mprotect,getpagesize,sync,cacheflush
网络管理类系统调用
getdomainname,setdomainname,gethostid,sethostid,gethostname,sethostname,socketcall,socket,bind,connect,accept,send,sendto,sendmsg,recv,recvfrom,recvmsg,listen,select,shutdown,getsockname,getpeername,getsockopt,setsockopt,sendfile,socketpair
用户管理类系统调用
getuid,setuid,getgid,setgid,getegid,setegid,geteuid,seteuid,setregid,setreuid,getresgid,setresgid,getresuid,setresuid,setfsgid,setfsuid,getgroups,setgroups
进程间通信类系统调用
sigaction,sigprocmask,sigpending,sigsuspend,signal,kill,sigblock,siggetmask,sigsetmask,sigmask,sigpause,sigvec,ssetmask,msgctl,msgget,msgsnd,msgrcv,pipe,semctl,semget,semop,shmctl,shmget,shmat,shmdt
总结:系统调用使用接口函数产生软中断,处理器进入中断状态执行相应的功能程序
原创文章,作者:小su,如若转载,请注明出处:https://www.sudun.com/ask/141077.html
用户评论
歆久
这篇文章讲得真细!我之前对Linux系统的体系结构了解不多,看了之后终于明白了内核和用户态之间的调用机制是如何运作的。
有15位网友表示赞同!
◆乱世梦红颜
以前总觉 linux 操作系统很复杂,看完了这篇,感觉其实原理还是蛮简单的,就是一层层抽象的过程呀。
有16位网友表示赞同!
打个酱油卖个萌
讲到系统调用表的时候,我突然想起之前的操作系统课。当时老师讲解得我还比较懵懂,现在看来这篇文章比那节课讲得更清楚啊。
有10位网友表示赞同!
心亡则人忘
这个系列博客写的太好了!期待你继续深入讲解Linux kernel的更多细节,比如中断处理机制、内存管理等等。
有10位网友表示赞同!
焚心劫
虽然我并没有使用过Linux内核编程,但阅读这篇博文让我对系统调用的概念有了更加清晰的认识。谢谢作者花了时间分享这方面的知识!
有15位网友表示赞同!
暮光薄凉
对于初学者来说,这篇博客写的太专业了,很多概念都比较抽象,希望以后能添加一些更易理解的例子说明。
有15位网友表示赞同!
Hello爱情风
文章的逻辑结构非常清晰合理,从高层概述到低层细节逐层深入,读起来比较轻松。我很喜欢这种循序渐进的讲解方式!
有13位网友表示赞同!
米兰
不知道为什么,我每次阅读Linux相关技术文章都感觉头疼,可能是我的理解能力不足吧。 期待能有更通俗易懂的解释。
有11位网友表示赞同!
苍白的笑〃
我觉得这篇博客对已经有了 Linux 调试经验的人来说比较有用,可以帮助他们更加深入地理解系统的底层机制。
有20位网友表示赞同!
封心锁爱
我一直在学习内核开发,这篇文章非常实用!它帮助我更好地理解了系统调用是如何实现的以及不同类型的系统调用之间的区别。
有9位网友表示赞同!
纯真ブ已不复存在
我之前想使用 syscall 实现一些功能,结果发现语法太复杂难以理解,这本书介绍的很详尽,也许就能帮我解决问题了
有8位网友表示赞同!
十言i
这个博客让我对Linux内核编程有了浓厚的兴趣,以后有机会一定要深入学习!
有9位网友表示赞同!
没过试用期的爱~
希望作者能在之后的文章中分享更多关于具体系统调用的实现细节,以及一些常见的系统调用错误分析方法。
有13位网友表示赞同!
来瓶年的冰泉
读完这篇文章,我更坚定了自己要学习 Linux 内核编程的决心!
有19位网友表示赞同!
窒息
感觉文章讲解了很多理论知识,但缺少了一些实际案例和代码示例。如果能结合实践应用来讲解会更好理解!
有13位网友表示赞同!
旧爱剩女
对系统调用的理解非常重要,这篇文章帮我巩固了这方面的概念,同时让我看到了 Linux 操作系统的强大之处!
有15位网友表示赞同!