`
coolerbaosi
  • 浏览: 724972 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

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-standardinput,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函数使用了LinuxWRITE系统调用,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>

源文档 <http://blog.csdn.net/mmz_xiaokong/article/details/5390372>


分享到:
评论

相关推荐

    C语言调用汇编的矩阵相乘

    在DSP++5.0条件下通过用C语言调用汇编子程序实现6*6的矩阵相乘

    AT&T汇编语言与GCC内嵌汇编简介LINUX内核架构

    AT&T汇编语言与GCC内嵌汇编简介 Linux内核体系架构(进程、系统调用)

    C语言与鲲鹏920处理器汇编语言混合编程

    本实验将通过三个部分介绍C调用汇编和C内嵌汇编两种混合编程方式以及ARM汇编的一些基础指令,ARM部分指令的详细介绍以及Linux常用命令请参考附录中的ARM指令以及Linux常用指令。 第一部分,介绍C语言调用汇编实现...

    Linux2.6内核标准教程(共计8-- 第1个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    Linux2.6内核标准教程(共计8--第6个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    Linux2.6内核标准教程(共计8--第8个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    Linux2.6内核标准教程(共计8--第3个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    Linux2.6内核标准教程(共计8--第7个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    Linux2.6内核标准教程(共计8--第4个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    Linux2.6内核标准教程(共计8--第2个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    Linux2.6内核标准教程(共计8--第5个)

    C.2.4 内嵌汇编格式——输出列表 361 C.2.5 内嵌汇编格式——输入列表 362 C.2.6 内嵌汇编格式——修饰字符 362 C.2.7 内嵌汇编格式——破坏描述 364 附录D 参考文献 366 D.1 关于IA32体系结构的资源 ...

    嵌入式Linux C编程入门(第2版) PPT

    9.1 linux系统调用及用户编程接口(api) 257 9.1.1 系统调用 257 9.1.2 用户编程接口(api) 257 9.1.3 系统命令 258 9.2 arm linux文件i/o系统概述 258 9.2.1 虚拟文件系统(vfs) 258 9.2.2 通用...

    linux内核 0.11版本源码 带中文注释

    // 如果定义了__LIBRARY__,则还包括系统调用号和内嵌汇编代码_syscall0()等。 #include &lt;time.h&gt; // 时间类型头文件。其中最主要定义了tm 结构和一些有关时间的函数原形。 /* * we need this inline - forking ...

    嵌入式课件

    9.5.4 C程序中内嵌汇编语句 9.5.5 从汇编程序中访问C程序变量 思考题与习题 第10 章 Bootloader 设计基础 10.1Bootloader 概述 10.1.1 Bootloader 的作用 10.1.2 Bootloader 的工作模式 10.1.3 Bootloader 的启动...

    通过Intel CPUID指令获取CPU信息

    使用内嵌汇编调用CPUID指令”的方式获取CPU信息。首先使用内嵌汇编调用CPUID指令来获取CPUID、CPU厂商、CPU Brand、CPU Family、CPU Model、CPU Stepping ID,然后采用MD5算法计算CPU信息的Hash值。

    实验报告1

    1、首先我们先进入内核查看系统调用表,找到空余的系统调用号以及系统调用表所对应的内存地址 2、关于修改寄存器的权限属性:利用内嵌汇编代码来实现 3、构造树形图函

    Linux1.0核心游记

    S4.1 内嵌汇编格式.........................................................................................25 S4.2 内嵌汇编示例........................................................................

    c语言编写单片机技巧

    答:对于复杂而开发时间紧的项目时,可以采用C语言,但前提是要求对该MCU系统的C语言和C编译器非常熟悉,特别要注意该C编译系统所能支持的数据类型和算法。虽然C语言是最普遍的一种高级语言,但不同的MCU厂家其...

    AT&T Assembly Language

    其中包括一些如何使用汇编链接C语言库,汇编调用系统调用,汇编执行浮点运算,C语言内嵌汇编等。 Chapter 1: What Is Assembly Language? 1 Processor Instructions 1 Instruction code handling 2 Instruction code...

Global site tag (gtag.js) - Google Analytics