资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,Linux,操作系统,内核实习简要说明,Linux,内核实习教学目标,通过本实验课程培养学生以下几方面的能力,:,会检查和分析,Linux,操作系统内部运行状态;,用自己的程序获取,Linux,操作系统内核数据结构;,重构,Linux,操作系统现存数据结构;,会添加自己的模块到,Linux,操作系统内核中去;,实习教材,Linux,操作系统内核实习,(,Kernel Projects for Linux,),Gary Nutt,著 潘登 等译,机械工业出版社,开发工具,gcc,编译器,gdb,调试器,Make,工程管理器,Vi,或其他文本编辑器,gcc,编译器常用选项,-o filename,指定可执行文件名,无此项则为,a.out,-c,只生成浮动目标文件(,.o,文件),-g,在可执行文件中包含标准调试信息,-,ggdb,在可执行文件中包含特有调试信息,-w,关闭所有警告性错误信息,-Wall,允许发出警告性错误信息,-D_KERNEL_,代码将在内核模式运行,-DMODULE,文件要给出适当的内核模块定义,gdb,调试器常用子命令,list,列出当前行上下,10,行文本,break,break,在程序中设置断点,run,启动被调试的程序,next,顺序执行到下一行语句,step,跟踪到函数体内下一语句,print,表达式,显示表达式的值,set,变量名,更改变量的值,delete ,删除指定断点号,实验练习,方法,学习一种,OS,如何工作的最好的方法是对他,行实验、阅读、修改和增强它的代码。但,OS,实验版本很容易损坏机器。本实验练习提供了一些尽量小的危险和简捷的练习方式:,在不修改该内核的情况下检查,OS,内部状态,通过编写自己的代码来阅读内核数据结构,重新实现现存数据结构,添加自己的函数和数据结构到,Linux,内核中,实验练习说明,每个练习都包含:,介绍:说明要练习的题目的一般概念和对应,Linux,中的实现代码的解释。,问题陈述:提出要解决的问题。及解决该问题的,Linux,相关信息。问题又易到难。,解决问题:提供基本的程序组织方案,是练习的主要框架。补充和完善这些框架则完成了这些练习。,实验练习内容,1,2,3,4,5,6,7,8,9,10,11,12,观察,Linux,内核行为,Shell,命令设计,内核定时器,内核模块,系统调用,共享内存,虚拟内存,同步机制,调度程序,设备驱动,文件系统,文件,I/O,注:不带,者可以选作,实验练习的要求,1,、给出实验程序的可执行文件,2,、要求写出具有以下内容的实验报告:,每个实验练习的解决方案,每个实验练习带注释的源代码文件,每个实验练习的现场调试记录,每个实验练习的现场测试记录,Linux,内核目录结构,linux,arch,drivers,i386,kernel,mm,fs,block,char,.,.,fs,kernel,.,kernel,目录,在,kernel,目录下文件实现了大多数,Linux,系统的核心函数,其中最重要、最主要的文件当属,sched.c,sched.c,文件定义的函数有:,调度程序,schedule,及相关操作;,等待队列的相关操作;,基准时钟的相关操作;,定时器相关操作任务队列的相关操作;,关于调度策略控制的,goodness,nice,等;,各种用户标识、组标识的,set,和,get,。,kernel,目录,关于进程控制的文件也位于此目录下。,fork.c,文件定义创建、克隆子进程的函数,do_fork,(),文件定义结束自身进程的,do_exit,(),等操作。,signal.c,文件中的函数都是关于信号控制的,如,(signal),操作,send_sig,。,ksyms.c,文件定义了,Linux,内核的输出符号表,使用模块功能时经常需要对这些符号表操作以实现模块和内核的动态链接。,kernel,目录,此外,该目录下还包含一些重要的文件,如:,time.c,,提供用户程序与系统之间关于时间的操作界面;,resource.c,,关于,I/O,端口资源,(port),的管理;,dma.c,,关于,DMA,通道的管理;,softirq.c,,关于,bottom half,队列、,softirq,等的操作;,itimer.c,,关于,itimer,定时器读写的系统调用;,printk.c,,展示系统工作参数的操作,如,printk,。,mm,目录,Linux,中独立于,CPU,体系结构特征的内存管理文件几乎都集中在,mm,目录下。如页式存储内存的分配和释放等等。,swap,c,文件 并不实现任何交换算法,它仅仅处理命令行选项“,swap=”,和“,buff=.,swapfile.c,文件 是管理交换文件和交换设备的源程序。它包含,swapon,、,swapoff,系统调用的执行程序,以及从交换空间申请空闲页面的操作,get_swap_page,。,page_io.c,文件 则实现了与交换空间低层的数据传输。,mm,目录,swap_state.c,-,文件,维护,swap cache,,它包含存储管理系统中最复杂、最难懂的操作和结构。,vmscan.c,文件,定义后台交换进程,kswapd,的代码,以及内存空间扫描函数,实现,Linux,页面换出策略,如,try_to_free_pages,。,所有的存储分配策略都在,mm,目录里实现。例:,slab.c,实现核心内存块,(,以数据结构,free area,表为入口,),的操作,kmalloc,(),、,kfree,(),等。,vmalloc.c,里包含,_,vmalloc,(),、,vfree,(),等函数。,page_alloc.c,里包含物理页面的申请函数,如页面申请函,_,get_free_pages,(),。,mm,目录,memory.c,文件,内存管理中低层核心函数,如缺页中断响应函数,do_no_page,(),,实现,Copy on,Write(COW,),特性的,do_wp_page,(),,以及众多页表管理函数。虚拟空间映射,(mapping),操作,do_mmap_pgoff,(),和,do_munmap,(),,以及系统调用,brk,的响应函数,,mmap.c,文件中都涉及进程虚拟空间地址的调整。,mremap.c,文件中包含,mremap,的操作代码。,mlock.c,文件实现四个关于内存,vma,段加锁操作的系统调用,mlock,、,munlock,、,mlockall,、,munlockall,。,mprotect.c,实现系统调用,mprotect,。,fs,目录,文件操作是所有,UNIX,系统都提供的基本功能。,fs,目录源程序涵盖各种类型的文件系统,各种类型的文件操作。,本目录下的每个子目录则是分门别类地描述某个特定的文件系统。,直接隶属本目录的文件分别是:,exec.c,。实现,execve,系统调用。其余五种关于装入执行程序的函数都由,C,语言库文件实现。,execve,支持脚本,(script),文件和多种格式的可执行文件。,fs,目录,devices.c,负责设备的注册和注销,定义缺省的打开设备操作和释放设备操作。,block_dev.c,。包含缺省的读、写设备操作。,super.c,。定义超级块的读操作,以及文件系统的安装、卸载操作。,inode.c,。,VFS,inode,的读写操作,以及维护,inode,cache,的程序。,dcache.c,。维护,dcache,的文件。,fs,目录,namei.c,。访问权限检查。根据路径检索,dentry,的相关操作,如,open_namei,、,path_walk,。,buffer.c,。实现,buff cache,的文件。,open.c,。文件的打开、关闭操作。系统调用,chown,、,chmod,、,fchown,、,fcbmod,、,chroot,、,chdir,、,fchdir,等也由该文件实现。,read_write.c,。系统调用,read,、,write,、,lseek,、,llseek,的源程序。,readdir.c,。读目录项的系统调用,readdir,和,getdents,由此文件实现。,fs,目录,select.c,。集中存放,select,操作的几乎全部源代码。,pipe.c,和,fifo.c,。实现管道,(pipe),和命名管道,(,fifo,),。除了,fifo_open,(),和,fifo_init,(),函数在,fifo.c,文件外,其余函数均存放于,pipe.c,文件。,ioctl.c,。实现系统调用,ioctl,。,fcntl.c,。实现关于,fcntl,操作命令的源代码。,dquot.c,。支持磁盘配额机制,(quota),。,arch,目录,与,CPU,类型相关的子目录和文件均集中安排在,arch,目录下。这里又有子目录,alpha,、,i386,、,m68k,、,mips,、,ppc,、,sparc,,每个子目录对应一种,CPU,,例如“,arch/i386,,就是关于,INTELCPU,的子目录。,include,目录,容纳,Linux,源程序的大多数是头文件,(header file),。其中,与平台无关的头文件在,include/,linux,子目录下,与,Intel CPU,相关的头文件在,include/asm-i386,子目录下。另外,还有关于,SCSI,设备的头文件目录,include/,scsi,和关于网络设备的头文件目录,include/net,。,除上述目录外,,Linux,源程序清单中还有,net,、,ipc,、,drivers,、,init,等目录,其内容不再一一介绍了。,重新编译内核,我们知道,linux,的内核代码受,GNU,通用公共许可证,(GPL),保护,是完全开放的,现在很多,linux,的网站都提供内核代码的下载。例如,,Linux,的官方网站,wWw.kernel.org,,在这里你可以找到所有的 内核版本。进入,www.kernel.org/pub/linux/kernel,/,,你会看到很多目录。例如在,www.kernel.org/pub/linux/kernel/v2.4/linux-2.4.18.tar.gz,下载一份,2.4.18,的内核。,解压出来,linux,源代码目录,用下面的命令把它放到某个目录,(,这里假设是,home,目录,),,并进行解压:,先将压缩包移到你所要放的目录下:,#,mv,linux-2.4.18.tar.gz/linux-2.4.18.tar.gz /home/,#tar,zxvf,linux-2.4.18.tar.gz,解压出来的是一个,linux,目录,里面就是,2.4.18,的内核源代码。,配置内核,在进行这项工作之前,不妨先看一看刚才我们解压出来的内核源代码目录下的,README,文件。在这份文件中,对怎样进行内核的解压、配置、安装都进行了详细的讲解。,在编译内核前,一般来说都需要对内核进行相应的配置。配置是精确控制新内核功能的机会。配置过程也控制哪些需编译到内核的二进制映象中,(,在启动时被载入,),,哪些是需要时才装入的内核模块,(module),。,配置内核,首先,重新建立一个核心需要将源代码树置于一种完整和一致的状态。因此,我们推荐执行命令,make,mrproper,。它将清除目录下所有配置文件和先前生成核心时产生的中间文件:,#,cd,linux,#make,mrproper,之后,我们就可以对内核进行配置了。有几种方法可以做这件事情:,#make,config,#make,menuconfig,#make,xconfig,#make,oldconfig,配置内核,下面分别对它们进行简单的解释,make,config,是基于文本的传统配置界面,该配置方式已逐渐被更为简单的,make,menuconfig,命令所取代,建议不要用这种方式。,make,menuconfig,是基于文本的选单式配置界面,是最为灵活的内核配置工具。我们一般使用这一配置命令。,make,xconfig,是基于图形窗口模式的配置界面,也是常用的配置命令,但它需要,xWindow,图形环境的支持。,make,oldconfig,用于在原来内核配置的基础上作些小的修改。,配置内核,进行配置时,大部分选项可以使用其缺省值,只有小部分需要根据用户不同的需要进行选择。例如,系统如果配有,SCSI,卡等,需要在网络配置中选择,SCSI,卡的支持。,对每一个配置选项,用户有三种选择,它们分别代表的含义如下:,Y -,将该功能编译进内核;,N -,不将该功能编译进内核;,M -,将该功能编译成可以在需要时动态插入到内核中的模块。,将与核心其他部分关系较远且不经常使用的部分功能代码编译成为可加载模块,有利于缩减内核的,减小内核消耗的内存,简化该功能相应的环境改变时对内核的影响。许多功能都可以这样处理,例如,上面提到的对,SCSI,卡的支持、对,ext2,等文件系统的支持等等。,编译内核和模块,配置完内核,接下来需要运行下列两条命令清除源代码树,产生相关文件:,#make,dep,#make clean,其中,make,dep,对内核源代码文件的依赖性和完整性进行检验,确保关键文件在正确的位置。,make clean,清除目标文件,确保所有有关文件都处于最新版本状态。,编译内核和模块,#make,将生成,vmlinux,内核文件,下面的命令将编译产生压缩形式的内核文件:,#make,zImage,在需要内核支持较多的外设和功能时,内核可能变得很大,此时可以用以下命令编译本内核产生压缩率更高的内核文件:,#make,bzImage,编译内核需要较长的时间,(,通常要几十分钟,),,具体与机器的硬件条件及内核的配置等因素有关。完成后产生的内核文件的位置在,linux/arch/i386/boot,目录下。,编译内核和模块,如果选择了可加载模块,编译完内核后,要对选择的模块进行编译:,#make modules,或用下面的命令编译模块并安装到标准的模块目录中:,#make modules install,模块在系统中的标准目录位于,/lib/modules/,x.y.z,,后面的,x.y.z,是版本号,为安全起见,在运行,#make,modules_instal,之前最好对,/lib/modules,进行备份,编译内核和模块,如果选择了可加载模块,编译完内核后,要对选择的模块进行编译:,#make modules,或用下面的命令编译模块并安装到标准的模块目录中:,#make modules install,模块在系统中的标准目录位于,/lib/modules/,x.y.z,,后面的,x.y.z,是版本号,为安全起见,在运行,#make,modules_instal,之前最好对,/lib/modules,进行备份,配置启动文件,通常,,linux,在系统引导后从,/boot,目录下读取内核映象到内存中。因此我们如果想要使用自己编译的内核,就必须先将它拷贝到,/boot,目录下。需要拷贝的文件包括压缩的内核映象文件,(,如前所述的,vmlinux,或,zImage,或,bzImage,),和内核符号表文件,(,System.map,),。,System.map,是,Linux,内核映象,(,zImage,),中的符号表文件。在这个文件中记录了内核映象中的符号及其对应的地址。系统正常启动时并不会读这个符号表,一般只在调试中才用到此文件。,配置启动文件,#cp/usr/src/linux/arch/i386/boot/zImage/boot/zImage-2.4.18,#cp/,usr/src/linux/System.map,/boot/System.map-2.4.18,#,ln,-,sf,/boot/System.map-2.4.18 /boot/,System.map,如果使用,lilo,启动,Linux,,则编辑,/etc/,lilo.conf,文件修改系统引导配置。,lilo,是一个,linux,的启动管理器,在机器启动硬件检测后被执行。这里我们以,lilo,为例通过增加一个配置项引导加载新内核,其他启动管理程序,(,例如,grub,配置,/boot/grub/,grub.conf,),。,配置启动文件,下面是一个,lilo.conf,的例子:,prompt,timeout,50,default,linux,boot,/dev/had,map,/boot/map,install,/boot/boot,b,message,/boot/message,lba32,配置启动文件,image,/boot/vmlinuz-2.4.7-10,label,linux,read-only,root,/dev/,hdal,image,/boot/zImage-2.4.18,label,newkernel,read-only,root=/dev/,hdal,查看,Linux,内核状况,在使用一个操作系统时我们经常会想先了解一下系统状况,比如,CPU,的型号、内存的配置等硬件信息。另外,我们还希望监测内存使用情况等动态的系统信息,以便清楚掌握系统运行情况。,由上一节我们知道普通进程是运行在用户态下的,它们无法直接访问内核数据来了解系统信息。内核提供了系统调用作为访问这些数据结构的统一接口,但这种程序如果编写不当可能存在安全上的隐患。在现代的操作系统中实现了一个,/proc,虚拟文件系统,通过它里面的一些文件,可以获取系统状态信息并且修改某些系统的配置信息。,查看,Linux,内核状况,/proc,文件系统本身并不占用硬盘空间,它仅存在于内存之中,为操作系统本身和应用程序之间的通信提供了一个安全的界面。像,linux,内核可卸载模块都在,/proc,文件系统中创建实体。当我们在内核中添加了新功能或设备驱动时,经常需要得到一些系统状态的信息,一般这样的功能可能需要经过一些像,ioctl,(),的系统调用来完成。系统调用界面对于一些功能性的信息可能是适合的,因为应用程序必须将这些信息读出后再做一定的处理。但对于一些实时性的系统信息,例如内存的使用状况,或者是驱动设备的统计资料等,我们更需要一个比较简单易用的界面来取得它们。,系统信息,/proc,文件系统就是这样的一个界面,我们可以简单地用,cat,、,strings,程序来查看这些信息。例如,查看系统内存的使用状况可以用如下的命令:,#cat/proc/,meminfo,total,:,USed,:,free,:,shared,:,buffers,:,cached,:,Mem,:,63627264 61816832 1810432 8192 696320 48074752,Swap,:,67567616 9908224 57659392,MemTotal,62136kB,MemFree,1768kB,用命令,procinfo,可以显示出由这些文件的到大量信息。,#,procinfo,User:1:35:18.043.1%page in:8061989,disk1:229457r70958w,Nice:0:00:05.580.0%page out:2040076,/proc,根下文件和目录,文件,/,目录,描述,apm,高级电源管理信息,bus,包含了总线以及总线上设备信息的目录,子目录以总线类型组织,cmdline,内核的命令行参数,cpuinfo,CPU,信息,包括主频、类型等信息,devices,系统字符和块设备编号及驱动程序名,dma,正在使用的,DMA,通道,driver,组织了不同的驱动程序,execdomains,和安全相关的,execdomain,/proc,根下文件和目录,fb,framebuffer,设备,filesystem,系统支持的文件系统类型,fs,文件系统需要的参数,对,NFS/export,有效,ide,包含了,IDE,子系统信息的目录,interrupts,系统注册的中断信息,其内容包括中断号、收到的中断数、驱动器名等,iomem,内存映象,ioports,I/O,端口使用情况,irq,与,CPU,有关的中断掩码,/proc,根下文件和目录,kcore,内核的,core,文件映象,记录了系统物理内存情况。可以用,gdb,程序从中检查内核数据结构。该文件不是文本格式,不能用,cat,等文本查看器查看其内容,kmsg,内核消息,可以从该文件检索内核使用,printk,(),产生的消息。,ksystem,内核符号表,包括内核标识符地址和名称,loadavg,最近,1,分,,5,分,,15,分钟时候的系统平均负载量,locks,内核锁,记录与被打开的文件有关的锁信息,mdstat,被,md,设备驱动程序控制的,RAID,设备的信息,/proc,根下文件和目录,meminfo,内存信息,misc,杂项设备信息,modules,系统正在使用,module,信息,mounts,已经装载的文件系统,Net,保存网络信息的目录,partitions,硬盘分区情况,pci,PCI,总线上设备情况,SCSI,SCSI,设备信息,/proc,根下文件和目录,slabinfo,slab,池信息,stat,静态统计信息,包括,CPU,的使用情况、磁盘、页面、交换、启动时间等数据,swap,交换分区的使用情况,sys,可以更改的内核数据的目录,,sysvipc,和,SysV,IPC,相关数据文件目录,包括系统中消息队列,(,msg,文件,),、信,号量,(,sem,文件,),、共享内存,(,shm,文件,),的信息,tty,和终端相关数据,uptime,从系统启动到现在所经过的秒数及系统空闲时间,/proc/sys,下文件和目录,/proc/sys,目录是一个特殊的目录,它支持直接使用文件系统的写操作,完成对内核中预定的一些变量的改变,从而达到更改系统特性的目的。比如说需要增加系统同时打开文件的个数,以提高使用,Linux,作为文件服务系的性能,那么可以使用下面的语句:,#cat /proc/sys/,fs,/file-max,4096,#echo 8192proc/sys/,fs,/filemax,#cat/proc/sys/,fs,/file-max,8192,/proc/sys,下文件和目录,文件,/,目录名,描述,fs/dentry,-state,目录缓存的状态,fs/dquot,-nr,分配的磁盘配额项及空余项,fs,/file-max,系统能够分配的最大文件句柄数,即同时打开的文件数,fs,/file-nr,已分配、使用的和最大的文件句柄数,fs/inode,-nr,最大的,inode,数和已分配的,inode,数,fs/inode,-stat,已分配的,,inode,数、不在用的,inode,数等信息,/proc/sys,下文件和目录,fs,/super-max,系统能够分配的,super block,数,每个被加裁的文件系统都要一个,super,blcok,fs,/super-nr,已分配的,super block,数,kernel/acct,进程状号控制值,kernel/ctrl-alt-del,ctrl-alt-del,键的作用,kernel/,domainname,机器域名,kernel/hostname,主机名,kernel/,osrelease,内核版本号,/proc/sys,下文件和目录,kernel/,ostype,linux,kernel/,pacnic,内核应急超时值,kernel/,printk,内核消息日志级数,kernel/real-root-dev,根设备的数量,kernel/version,内核编译日期,net/core,通用网络参数,net,ipv4,IP,网络参数,vm/bdflush,磁盘缓冲区刷新参数,vm/freepages,磁盘缓冲区刷新参数,vm/freepages,最小自由页数,进程信息,当你进入,/proc,目录时,你会发现很多以十进制数为标题的目录,它们都是记录系统中正在运行的每个用户级进程的信息,,/,数字表示进程号,(,pid,),。,/proc/self,是当前进程目录的符号链接。这些目录下存放着许多有关进程信息的文件,比如,status,文件包含许多进程控制块,(PCB),中的进程状态信息,我们用,cat,命令显示如下:,进程信息,#cat/proc/self/status,Name,:,cat,State,:,R(running,),Pid,:,890l,PPid,:,8779,TracerPid,:,0,Uid,:,0 0 0 0,Gid,:,0 0 O 0,FDSize,:,256,Groups,:,0 1 2 3 4 6 10,进程目录下文件和目录,文件,/,目录名,描述,cmdline,该进程的命令行参数,cwd,进程运行的当前路径的符号链接,environ,该进程运行的环境变量,exe,该进程相关的程序文件的符号链接,fd,包含了所有该进程使用的文件描述符的目录,进程目录下文件和目录,maps,可执行程序或者库文件对应的内存映象,mem,该进程使用的内存,root,该进程所有者的家,(home),目录,stat,进程状态,statm,进程的内存状态,status,用易读的方式表示的进程状态,Linux,汇编语法格式,绝大多数,Linux,程序员以前只接触过,DOS/Windows,下的汇编语言,这些汇编代码都是,Intel,风格的。但在,Unix,和,Linux,系统中,更多采用的还是,AT&T,格式,两者在语法格式上有着很大的不同:,Linux,汇编语法格式,在,AT&T,汇编格式中,寄存器名要加上,%,作为前缀;而在,Intel,汇编格式中,寄存器名不需要加前缀。例如:,AT&T,格式,Intel,格式,pushl,%,eax,push,eax,Linux,汇编语法格式,在,AT&T,汇编格式中,用,$,前缀表示一个立即操作数;而在,Intel,汇编格式中,立即数的表示不用带任何前缀。例如:,AT&T,格式,Intel,格式,pushl,$1,push 1,Linux,汇编语法格式,AT&T,和,Intel,格式中的源操作数和目标操作数的位置正好相反。在,Intel,汇编格式中,目标操作数在源操作数的左边;而在,AT&T,汇编格式中,目标操作数在源操作数的右边。例如:,AT&T,格式,Intel,格式,addl,$1,%,eax,add,eax,1,Linux,汇编语法格式,在,AT&T,汇编格式中绝对转移和调用指令(,jump/call,)的操作数前要加上,*,作为前缀,而在,Intel,格式中则不需要。,远程转移指令和远程子调用指令的操作码,在,AT&T,汇编格式中为,ljump,和,lcall,,而在,Intel,汇编格式中则为,jmp,far,和,call far,,即:,AT&T,格式,Intel,格式,ljump,$section,$offset,jmp,far,section:offset,lcall,$section,$offset,call far,section:offset,与之相应的远程返回指令则为:,AT&T,格式,Intel,格式,lret,$,stack_adjust,ret far,stack_adjust,Linux,汇编语法格式,在,AT&T,汇编格式中,内存操作数的寻址方式是,section:disp(base,index,scale),而在,Intel,汇编格式中内存操作数的寻址方式为:,section:base,+index*scale+,disp,由于,Linux,工作在保护模式下,用的是,32,位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计算方法:,disp,+base+index*scale,GCC,内联汇编,用汇编编写的程序虽然运行速度快,但开发速度非常慢,效率也很低。如果只是想对关键代码段进行优化,或许更好的办法是将汇编指令嵌入到,C,语言程序中,从而充分利用高级语言和汇编语言各自的特点。但一般来讲,在,C,代码中嵌入汇编语句要比,纯粹,的汇编语言代码复杂得多,因为需要解决如何分配寄存器,以及如何与,C,代码中的变量相结合等问题。,GCC,提供了很好的内联汇编支持,最基本的格式是:,_,asm_(asm,statements);,GCC,内联汇编,例如:,_,asm_(nop,);,如果需要同时执行多条汇编语句,则应该用,nt,将各个语句分隔开,例如:,_,asm,_(,pushl,%,eax,nt,movl,$0,%,eax,nt,popl,%,eax,);,GCC,内联汇编,通常嵌入到,C,代码中的汇编语句很难做到与其它部分没有任何关系,因此更多时候需要用到完整的内联汇编格式:,_,asm_(asm,statements:outputs:inputs:registers-modified);,插入到,C,代码中的汇编语句是以,:,分隔的四个部分,其中第一部分就是汇编代码本身,通常称为指令部,其格式和在汇编语言中使用的格式基本相同。指令部分是必须的,而其它部分则可以根据实际情况而省略。,GCC,内联汇编,在将汇编语句嵌入到,C,代码中时,操作数如何与,C,代码中的变量相结合是个很大的问题。,GCC,采用如下方法来解决这个问题:程序员提供具体的指令,而对寄存器的使用则只需给出,样板,和约束条件就可以了,具体如何将寄存器与变量结合起来完全由,GCC,和,GAS,来负责。,在,GCC,内联汇编语句的指令部中,加上前缀,%,的数字,(,如,%0,,,%1),表示的就是需要使用寄存器的,样板,操作数。指令部中使用了几个样板操作数,就表明有几个变量需要与寄存器相结合,这样,GCC,和,GAS,在编译和汇编时会根据后面给定的约束条件进行恰当的处理。由于样板操作数也使用,%,作为前缀,因此在涉及到具体的寄存器时,寄存器名前面应该加上两个,%,,以免产生混淆。,GCC,内联汇编,紧跟在指令部后面的是输出部,是规定输出变量如何与样板操作数进行结合的条件,每个条件称为一个,约束,,必要时可以包含多个约束,相互之间用逗号分隔开就可以了。每个输出约束都以,=,号开始,然后紧跟一个对操作数类型进行说明的字后,最后是如何与变量相结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行完嵌入的汇编代码后均不保留执行之前的内容,这是,GCC,在调度寄存器时所使用的依据。,GCC,内联汇编,输出部后面是输入部,输入约束的格式和输出约束相似,但不带,=,号。如果一个输入约束要求使用寄存器,则,GCC,在预处理时就会为之分配一个寄存器,并插入必要的指令将操作数装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行完嵌入的汇编代码后也不保留执行之前的内容。,有时在进行某些操作时,除了要用到进行数据输入和输出的寄存器外,还要使用多个寄存器来保存中间计算结果,这样就难免会破坏原有寄存器的内容。在,GCC,内联汇编格式中的最后一个部分中,可以对将产生副作用的寄存器进行说明,以便,GCC,能够采用相应的措施。,GCC,内联汇编,下面是一个内联汇编的简单例子:,/*,inline.c,*/,int,main(),int,a=10,b=0;,_,asm,_ _,volatile_(movl,%1,%,eax;nr,movl,%,eax,%0;“,:=,r(b,)/*,输出*,/,:,r(a,)/*,输入*,/,:%,eax,);/*,不受影响的寄存器*,/,printf(Result,:%d,%,dn,a,b);,GCC,内联汇编,上面的程序完成将变量,a,的值赋予变量,b,,有几点需要说明:,变量,b,是输出操作数,通过,%0,来引用,而变量,a,是输入操作数,通过,%1,来引用。,输入操作数和输出操作数都使用,r,进行约束,表示将变量,a,和变量,b,存储在寄存器中。输入约束和输出约束的不同点在于输出约束多一个约束修饰符,=,。,在内联汇编语句中使用寄存器,eax,时,寄存器名前应该加两个,%,,即,%,eax,。内联汇编中使用,%0,、,%1,等来标识变量,任何只带一个,%,的标识符都看成是操作数,而不是寄存器。,内联汇编语句的最后一个部分告诉,GCC,它将改变寄存器,eax,中的值,,GCC,在处理时不应使用该寄存器来存储任何其它的值。,由于变量,b,被指定成输出操作数,当内联汇编语句执行完毕后,它所保存的值将被更新。,GCC,内联汇编,在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从,0,开始,每个约束记数一次,指令部要引用这些操作数时,只需在序号前加上,%,作为前缀就可以了。需要注意的是,内联汇编语句的指令部在引用一个操作数时总是将其作为,32,位的长字使用,但实际情况可能需要的是字或字节,因此应该在约束中指明正确的限定符:,GCC,内联汇编,限定符,意义,m,、,v,、,o,内存单元,r,任何寄存器,q,寄存器,eax,、,ebx,、,ecx,、,edx,之一,i,、,h,直接操作数,E,和,F,浮点数,g,任意,a,、,b,、,c,、,d,分别表示寄存器,eax,、,ebx,、,ecx,和,edx,S,和,D,寄存器,esi,、,edi,I,常数(,0,至,31,),
展开阅读全文