您的位置:首页 > 运维架构 > Linux

Linux系统调用(C内嵌汇编)

2012-03-31 18:07 232 查看
Linux下对文件操作有两种方式:系统调用(systemcall)和库函数调用(Library functions)。可以参考《Linux程序设计》(英文原版为《Beginning LinuxProgramming》,作者是Neil Matthew和Richard Stones)第三章: Working with files。

1.系统调用

系统调用提供的函数如open, close,read, write, ioctl等,需包含头文件unistd.h。以write为例:其函数原型为 size_t write(int fd, constvoid *buf, size_t nbytes),其操作对象为文件描述符或文件句柄fd(filedescriptor),要想写一个文件,必须先以可写权限用open系统调用打开一个文件,获得所打开文件的fd,例如fd=open(/"/dev/video/", O_RDWR)。fd是一个整型值,每新打开一个文件,所获得的fd为当前最大fd加1。Linux系统默认分配了3个文件描述符值:0-standard input,1-standardoutput,2-standard
error。

系统调用通常用于底层文件访问(low-levelfile access),例如在驱动程序中对设备文件的直接访问。

系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。

系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。事实上,即使在用户空间使用库函数来对文件进行操作,因为文件总是存在于存储介质上,因此不管是读写操作,都是对硬件(存储器)的操作,都必然会引起系统调用。也就是说,库函数对文件的操作实际上是通过系统调用来实现的。例如C库函数fwrite()就是通过write()系统调用来实现的。

这样的话,使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?这是因为,读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。

2.库函数调用

标准C库函数提供的文件操作函数如fopen,fread, fwrite, fclose, fflush, fseek等,需包含头文件stdio.h。以fwrite为例,其函数原型为size_tfwrite(const void *buffer, size_t size, size_t item_num, FILE*pf),其操作对象为文件指针FILE*pf,要想写一个文件,必须先以可写权限用fopen函数打开一个文件,获得所打开文件的FILE结构指针pf,例如pf=fopen(/"~/proj/filename/",/"w/")。实际上,由于库函数对文件的操作最终是通过系统调用实现的,因此,每打开一个文件所获得的FILE结构指针都有一个内核空间的文件描述符fd与之对应。同样有相应的预定义的FILE指针:stdin-standardinput,stdout-standard
output,stderr-standard error。

库函数调用通常用于应用程序中对一般文件的访问。

库函数调用是系统无关的,因此可移植性好。

由于库函数调用是基于C库的,因此也就不可能用于内核空间的驱动程序中对设备的操作

eg:

char *str = "Helloworld!/n";
void print()
{
asm( "movl $13,%%edx \n\t"
"movl %0,%%ecx \n\t"
"movl $0,%%ebx \n\t"
"movl $4,%%eax \n\t"
"int $0x80 \n\t"
::"r"(str):"edx","ecx","ebx");
}
void exit()
{
asm( "movl$42,%ebx \n\t"
"movl $1,%eax\n\t"
"int $0x80 \n\t");
}
void nomain()
{
print();
exit();
}


代码大概意思是nomain()是入口,然后调用print()函数,打印"Helloworld”,接着调用exit()函数,结束进程。这里的print函数使用了Linux的WRITE系统调用,exit使用了EXIT系统调用,用内联汇编实现。

连接命令如下:

gcc –c -fno-builtin hello.c

ld–static –e nomain –o hello hello.o

注意,这里控制了链接器的行为,用-e指定了入口函数为nomain

那么问题来了系统调用参数怎么传递的?不懂:google了一把,找到了下面解释

C或汇编语言中也需要通过某些途径来使用操作系统提供的服务,也就是系统调用;系统调用就是通过与操作系统内核通信来完成;系统调用会把用户态程序的调用转换成对系统内核服务的调用;

Linux平台下有两种方式来使用系统调用:一种是利用封装后的C库(libc),另一种是通过汇编直接调用;其中,通过汇编语言来直接调用系统调用,是最高效地使用Linux内核服务的方法,因为最终生成的程序不需要与任何库进行连接,而是直接与内核通信;

与DOS一样,Linux下的系统调用也是通过中断(int0x80)方式来实现的;

在执行"int$0x80"指令时,寄存器eax中存放的是系统调用的功能号(即:中断功能号,DOS下存放在AH中),所有的系统调用功能号都可以再文件/usr/include/bits/syscall.h中找到,为了便于使用,它们都是用"SYS_<name>"这样的宏来定义的;如:SYS_write、SYS_exit等;

系统调用的参数传递规则:

传递给系统调用的参数则必须按照参数顺序依次存放到寄存器ebx,ecx,edx,esi,edi中,当系统调用完成之后,返回值存放在eax中;

A.当系统调用所需参数的个数不超过5个的时候,执行"int$0x80"指令时,需在eax中存放系统调用的功能号,传递给系统调用的参数则按照参数顺序依次存放到寄存器ebx,ecx,edx,esi,edi中,当系统调用完成之后,返回值存放在eax中;

比如,经常用到的write函数的定义如下:

ssize_twrite(int fd, const void* buf, size_t count);

该函数的功能最终通过SYS_write这一系统调用来实现的;根据上面的参数传递规则可知,参数fd存放在ebx中,参数buf存放在ecx中,参数count存放在edx中,而系统调用功能号SYS_write则存放在寄存器eax中;系统调用执行完成之后,返回值可以从eax中得到;

B.当系统调用的参数超过5个的时候,执行"int$0x80"指令,需在eax中存放系统调用的功能号,所不同的只是全部的参数应该依次存放在一块连续的内存区域里,同时在寄存器ebx中保存指向该内存区域的指针(即:该连续内存块的首地址),返回值仍然保存在寄存器eax中;由于只是需要一块连续的内存区域来保存系统调用所需要的参数,因此,完全可以像普通的函数调用一样使用栈来传递系统调用所需要的参数;但是要注意一点:Linux采用的是C语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即:最后一个参数最先进栈,而第一个参数最后进栈;如果采用栈来传递系统调用所需要的参数,在执行"int$0x80"指令时,还应将栈指针的当前值(栈顶地址)复制到寄存器ebx中;

例如,系统调用mmap()的参数个数就超过5个:

void*mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);

使用这个系统调用时,系统调用功能号保存到eax中,mmap()所需要的所有参数存放到一块连续的内存区域中,这块连续内存区域的首地址存放到ebx中,即可;

C库函数的参数传递规则:

1、参数从右向左依次入栈(push);

2、库函数的返回值存放在寄存器eax中;

3、系统调用exit()的参数值在exit()调用结束程序退出的时候会被传递给系统shell,通过打印$?的值可看到;

汇编函数的返回值:

只要是函数,一般都需要返回值;汇编语言中约定:对于32位的返回值,存放在寄存器eax中返回;对于64位的返回值,存放在寄存器eax和edx中返回,高位字放在edx中,低位字放在eax中;

参考链接

源文档 <http://blog.csdn.net/yming0221/article/details/6314152>

源文档 </article/8527168.html>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: