syscall系统调用

syscall系统调⽤
5.1.5  如何使⽤系统调⽤
如图5.2所⽰,⽤户应⽤可以通过两种⽅式使⽤系统调⽤。第⼀种⽅式是通过C库函数,包括系统调⽤在C库中的封装函数和其他普通函数。图5.2  使⽤系统调⽤的两种⽅式
第⼆种⽅式是使⽤_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)
其中,type表⽰所⽣成系统调⽤的返回值类型,name表⽰该系统调⽤的名称,typeN、argN分别表⽰第N个参数的类型和名称,它们的数⽬和_syscall后⾯的数字⼀样⼤。这些宏的作⽤是创建名为name的函数,_syscall后⾯跟的数字指明了该函数的参数的个数。
⽐如sysinfo系统调⽤⽤于获取系统总体统计信息,使⽤_syscall宏定义为:
1. _syscall1(int, sysinfo, struct sysinfo *, info);
展开后的形式为:
1. int sysinfo(struct sysinfo * info)
2. {
3.    long __res;
4.    __asm__ volatile("int $0x80" : "=a" (__res) : "0" (116),"b" ((long)(info)));
5.    do {
6.        if ((unsigned long)(__res) >= (unsigned long)(-(128 + 1))) {
7. errno = -(__res);
8. __res  = -1;
9.        }
二次密封
10.        return (int) (__res);
11.    } while (0);
12. }
可以看出,_syscall1(int, sysinfo, struct sysinfo *, info)展开成⼀个名为sysinfo的函数,原参数int就是函数的返回类型,原参数struct sysinfo *和info分别构成新函数的参数。
在程序⽂件⾥使⽤_syscall宏定义需要的系统调⽤,就可以在接下来的代码中通过系统调⽤名称直接调⽤该系统调⽤。下⾯是⼀个使⽤sysinfo系统调⽤的实例。
代码清单5.1  sysinfo系统调⽤使⽤实例
2. 01 #include <stdlib.h>
3. 02 #include <errno.h>
4. 03 #include <linux/unistd.h>
5. 04 #include <linux/kernel.h>      /* for struct sysinfo */
6. 05
7. 06 _syscall1(int, sysinfo, struct sysinfo *, info);
8.      07
9. 08 int main(void)
10. 09 {
11. 10    struct sysinfo s_info;
12. 11    int error;
13. 12
14. 13    error = sysinfo(&s_info);
15. 14    printf("code error = %d/n", error);
16. 15    printf("Uptime = %lds/nLoad: 1 min %lu / 5 min %lu / 15 min %lu/n"
17. 16        "RAM: total %lu / free %lu / shared %lu/n"
18. 17        "Memory in buffers = %lu/nSwap: total %lu / free %lu/n"
19. 18        "Number of processes = %d/n",
20. 19        s_info.uptime, s_info.loads[0],
21. 20        s_info.loads[1], s_info.loads[2],
22. 21        alram, s_info.freeram,
抛物面雷达物位计
23. 22        s_info.sharedram, s_info.bufferram,
24. 23        alswap, s_info.freeswap,
25. 24        s_info.procs);
26. 25        exit(EXIT_SUCCESS);
27.      26 }
但是⾃2.6.19版本开始,_syscall宏被废除,我们需要使⽤syscall函数,通过指定系统调⽤号和⼀组参数来调⽤系统调⽤。syscall函数原型为:
1. int syscall(int number, ...);
其中number是系统调⽤号,number后⾯应顺序接上该系统调⽤的所有参数。下⾯是gettid系统调⽤的调⽤实例。
代码清单5.2  gettid系统调⽤使⽤实例
2. 01 #include <sys/syscall.h>
3. 02 #include <sys/types.h>
4. 03
5. 04 #define __NR_gettid      224
6. 05
7. 06 int main(int argc, char *argv[])
8. 07 {
9. 08    pid_t tid;
10. 09
11. 10    tid = syscall(__NR_gettid);
12. 11 }储槽
⼤部分系统调⽤都包括了⼀个SYS_符号常量来指定⾃⼰到系统调⽤号的映射,因此上⾯第10⾏可重写为:
1. tid = syscall(SYS_gettid);
指挥大厅控制台
</pre><pre class="alt" name="code" ><p><strong>5.2 系统调⽤执⾏过程</strong></p><p>系统调⽤的执⾏过程主要包括如图5.3与图5.4所⽰的两个阶段:⽤户空间到内核空间的转换阶段,以及系统调⽤处理程序system_call函数到系统调⽤服务例程的阶段。</p><table class="ln" border="1"
cellspacing="0" align="center" bgcolor="#ddddd0"><tbody><tr><td bgcolor="#ffffff"><a target=_blank
href="images.51cto/files/uploadimg/20100723/130828838.jpg" target="_blank" ><img class="fit-image" src="images.51cto/files/uploadimg/20100723/130828838.jpg" border="0" alt="" width="353" height="331" /></a> <a target=_blank href="blog.csdn/files/uploadimg/20060921/153223104.gif" target="_blank" style="color:
rgb(51, 102, 153); text-decoration: none;"></a></td></tr><tr><td class="it" align="center" bgcolor="#dddddd">图5.3  ⽤户空间到内核空间</td></tr>
</tbody></table><table class="ln" border="1" cellspacing="0" align="center" bgcolor="#ddddd0"><tbody><tr><td bgcolor="#ffffff"><a target=_blank
href="images.51cto/files/uploadimg/20100723/130850432.jpg" target="_blank" ><img class="fit-image" src="images.51cto/files/uploadimg/20100723/130850432.jpg" border="0" alt="" width="437" height="258" /></a> <a target=_blank href="blog.csdn/files/uploadimg/20060921/153223104.gif" target="_blank" ></a></td></tr><tr><td class="it" align="center" bgcolor="#dddddd">图5.4  system_call函数到系统调⽤服务例程</td></tr></tbody></table><p><strong>(1)⽤户空间到内核空间。</strong></p><p>如图5.3所⽰,系统调⽤的执⾏需要⼀个⽤户空间到内核空间的状态转换,不同的平台具有不同的指令可以完成这种转换,这种指令也被称作操作系统陷⼊(operating system trap)指令。</p><p>Linux通过软中断来实现这种陷⼊,具体对于X86架构来说,是软中断0x80,也即int $0x80汇编指令。软中断和我们常说的中断(硬件中断)不同之处在
于-它由软件指令触发⽽并⾮由硬件外设引发。</p><p>int 0x80指令被封装在C库中,对于⽤户应⽤来说,基于可移植性的考虑,不应该直接调⽤int $0x80指令。陷⼊指令的平台依赖性,也正是系统调⽤需要在C库进⾏封装的原因之⼀。</p><p>通过软中断0x80,系统会跳转到⼀个预设的内核空间地址,它指向了系统调⽤处理程序(不要和系统调⽤服务例程相混淆),即在arch/i386/kernel/entry.S⽂件中使⽤汇编语⾔编写的system_call函数。</p><p><strong>(2)system_call函数到系统调⽤服务例程。</strong></p><p>很显然,所有的系统调⽤都会统⼀跳转到这个地址进⽽执⾏system_call函数,但正如前⾯所述,到2.6.23版为⽌,内核提供的系统调⽤已经达到了325个,那么system_call函数⼜该如何派发它们到各⾃的服务例程呢?</p><p>软中断指令int 0x80执⾏时,系统调⽤号会被放⼊eax寄存
器,同时,sys_call_table每⼀项占⽤4个字节。这样,如图5.5所⽰,system_call函数可以读取eax寄存器获得当前系统调⽤的系统调⽤号,将其乘以4⽣成偏移地址,然后以sys_call_table为基址,基址加上偏移地址所指向的内容即是应该执⾏的系统调⽤服务例程的地址。</p><p>另外,除了传递系统调⽤号到eax 寄存器,如果需要,还会传递⼀些参数到内核,⽐如write系统调⽤的服务例程原型为:</p><pre > <ol class="dp-xml"><li class="alt">sys_write(unsigned int fd, const char * buf, size_t count); </li></ol>
调⽤write系统调⽤时就需要传递⽂件描述符fd、要写⼊的内容buf以及写⼊字节数count等⼏个内容到
内核。ebx、ecx、edx、esi以及edi 寄存器可以⽤于传递这些额外的参数。
正如之前所述,系统调⽤服务例程定义中的asmlinkage标记表⽰,编译器仅从堆栈中获取该函数的参数,⽽不需要从寄存器中获得任何参数。进⼊system_call函数前,⽤户应⽤将参数存放到对应寄存器中,system_call函数执⾏时会⾸先将这些寄存器压⼊堆栈。
对于系统调⽤服务例程,可以直接从system_call函数压⼊的堆栈中获得参数,对参数的修改也可以⼀直在堆栈中进⾏。在system_call函数退出后,⽤户应⽤可以直接从寄存器中获得被修改过的参数。
并不是所有的系统调⽤服务例程都有实际的内容,有⼀个服务例程sys_ni_syscall除了返回-ENOSYS外不做任何其他⼯作,在
kernel/sys_ni.c⽂件中定义。
1. 10 asmlinkage long sys_ni_syscall(void)
2. 11 {
3. 12      return -ENOSYS;
4. 13 }
sys_ni_syscall的确是最简单的系统调⽤服务例程,表⾯上看,它可能并没有什么⽤处,但是,它在sys_call_table中占据了很多位置。多数位置上的sys_ni_syscal都代表了那些已经被内核中淘汰的系统调⽤,⽐如:
1. .long sys_ni_syscall    /* old stty syscall holder */
2. .long sys_ni_syscall    /* old gtty syscall holder */
就分别代替了已经废弃的stty和gtty系统调⽤。如果⼀个系统调⽤被淘汰,它所对应的服务例程就要被指定为sys_ni_syscall。
我们并不能将它们的位置分配给其他的系统调⽤,因为⼀些⽼的代码可能还会使⽤到它们。否则,如果某个⽤户应⽤试图调⽤这些已经被淘汰的系统调⽤,所得到的结果,⽐如打开了⼀个⽂件,就会与预期完全不同,这将令⼈感到⾮常奇怪。
其实,sys_ni_syscall中的"ni"即表⽰"not implemented(没有实现)"。
系统调⽤通过软中断0x80陷⼊内核,跳转到系统调⽤处理程序system_call函数,并执⾏相应的服务例程,但由于是代表⽤户进程,所以这个执⾏过程并不属于中断上下⽂,⽽是处于进程上下⽂。
因此,系统调⽤执⾏过程中,可以访问⽤户进程的许多信息,可以被其他进程抢占(因为新的进程可能使⽤相同的系统调⽤,所以必须保证系统调⽤可重⼊),可以休眠(⽐如在系统调⽤阻塞时或显式调⽤schedule函数时)。
这些特点涉及进程调度的问题,在此不做深究,读者只需要理解当系统调⽤完成后,把控制权交回到发起调⽤的⽤户进程前,内核会有⼀次调度。如果发现有优先级更⾼的进程或当前进程的时间⽚⽤完,那么就会选择⾼优先级的进程或重新选择进程运⾏。
5.3 系统调⽤⽰例
本节通过对⼏个系统调⽤的剖析来讲解它们的⼯作⽅式。
5.3.1  sys_dup
dup系统调⽤的服务例程为sys_dup函数,在fs/fcntl.c⽂件中定义如下。
代码清单5.3  dup系统调⽤的服务例程

本文发布于:2024-09-23 03:17:31,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/4/197016.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:系统   函数   参数   进程   服务例程   指令
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议