基本的行内汇编一般是按照格式:asm("statements"),如asm("nop"),asm("cli"),asm与__asm__是完全一样的。如果有多行汇编,则每一行都要加上引号" "。
基本的格式是:asm ( "statements" : output_regs : input_regs : clobbered_regs);
其中,
Ø clobbered_regs指的是被改变的寄存器。
Ø“statements”为汇编语句本身,其格式与在汇编语言程序中使用的基本相同,是必须有的,而其他各部分则可视具体的情况而省略。
示例:向buf中写上count个value值。
int count=1;
int value=1;
int buf[10];
void main()
{
asm(
"cld " //汇编语句本身
"rep "
"stosl"
: //输出寄存器
: "c" (count), "a" (value) , "D" (buf[0]) //输入寄存器
: "%ecx","%edi" ); //改变的寄存器
}
main()函数对应的汇编代码如下:
movl count,%ecx
movl value,%eax
movl buf,%edi
#APP
cld
rep
stosl
#NO_APP
这段代码将count的值输入到c对应的寄存器(ecx),将value的值输入到a对应的寄存器(eax),将buf的值输入到D对应的寄存器(edi),cld、rep及stos几条语句的功能是向buf中写上count个value值。其中符号"c"(count)指示要把count的值放入ecx寄存器。
行内汇编中常用符号及其含义如表1.1所示。
表1.1 行内汇编中常用符号及其含义
|
符号 |
代表的寄存器或值 |
符号 |
代表的寄存器或值 |
|
a |
eax |
D |
edi |
|
b |
ebx |
I |
常数值,(0~31) |
|
c |
ecx |
q,r |
动态分配的寄存器 |
|
d |
edx |
g |
eax, ebx, ecx, edx或内存变量 |
|
S |
esi |
A |
把eax和edx合成一个64位的寄存器(use long longs) |
示例:让gcc自行选择合适的寄存器。
asm("leal (%1,%1,4),%0"
: "=r" (x)
: "0" (x) );
这段代码实现5*x的快速乘法。对应的汇编代码为:
movl x,%eax
#APP
leal (%eax,%eax,4),%eax
#NO_APP
movl %eax,x
在表1-1中,使用q指示编译器从eax, ebx, ecx, edx分配寄存器。使用r指示编译器从eax, ebx, ecx, edx, esi, edi分配寄存器。“=”表示输出寄存器。数字%n代表寄存器,数字的大小表示寄存器在输出及输入语句行中依次出现的序号(从0开始计数)。如果使用固定的寄存器,则为%+寄存器名,如%ebx,则这段代码变为:
asm("leal (%%ebx,%%ebx,4),%0"
: "=r" (x)
: "0" (x) );
注意在此处要使用两个%,因为一个%的语法已经被%n用掉了。
示例:替代系统调用的名字。
下面所列的这段代码,用实际的系统调用名字加上前缀__NR_后返回。在一般情况下,加下划线是指全局变量。
#define _syscall1(type,name,type1,arg1)
type name(type1 arg1)
{
long __res; // __res应该是一个全局变量
// volatile 的意思是不允许优化,使编译器严格按照你的汇编代码汇编
__asm__ volatile ("int $0x80"
// 产生代码 movl %eax, __res
: "=a" (__res)
/*用实际的系统调用名字代替"name",然后再把__NR_...展开。
接着把展开的常数放入eax,把arg1放入ebx */
: "0" (__NR_##name),"b" ((long)(arg1)));
if (__res >= 0)
return (type) __res;
errno = -__res;
return -1;
}
1.51.5 内核中的时间延迟
操作系统通过时钟中断来确定时间间隔。时钟中断由系统计时硬件以周期性的间隔产生,这个间隔由内核根据Hz值设定,Hz是一个与体系结构有关的常数,在文件<Linux/param.h>中定义。当前的Linux版本为大多数平台定义的Hz值是100,在某些平台上是1 024。在内核代码中尽量使用与平台无关的Hz值来计数。
当时钟中断发生时,变量jiffies的值就增加。jiffies在系统启动时初始化为0,因此,jiffies值就是自操作系统启动以来的时钟嘀嗒的数目,jiffies在头文件<Linux/sched.h>中被定义成数据类型为unsigned long volatile型变量,当jiffies达到最大值时,它又返回到0。
大多数CPU都有一个高精度的计数器,通过计数寄存器可以读取或改变它的值。如x86的寄存器TSC(timestamp counter,时间戳计数器)是一个64位寄存器,记录CPU时钟周期数,包含了头文件<asm/msr.h>,就可以使用如下的宏:
rdtsc(low,high); //原子操作性地把 64 位的数值读到两个 32 位变量中
rdtscl(low); //只把寄存器的低半部分读入一个 32 位变量
例如,下面这段代码可以测量该指令自身的运行时间:
unsigned long ini, end;
rdtscl(ini); rdtscl(end);
printk("time lapse: %li\n", end - ini);
在内核头文件中还有一个与体系结构无关的函数用做高精度的计数器,列出如下:
#include <Linux/timex.h>
cycles_t get_cycles(void);
cycles_t类型是能装入对应CPU单个寄存器的合适的无符号类型。对于x86的时钟周期计数器只返回低32位。
内核一般通过jiffies值来获取当前时间。利用jiffies值来测量时间间隔在大多数情况下已经足够了。
获取当前时间,可以使用do_gettimeofday函数。该函数返回用秒或微秒值来填充的指向 struct timeval 的指针变量。do_gettimeofday列出如下:
#include <Linux/time.h>
void do_gettimeofday(struct timeval *tv);
如果驱动程序使用等待队列等待某个事件,而你又想确保在一段时间后一定运行该驱动程序,可以使用sleep函数的超时版本:
sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t*q, unsigned long timeout);
两种实现都能让进程在指定的等待队列上睡眠,并在超时期限(用jiffies表示)到达时返回。如果驱动程序无须等待其他事件,可以用一种更直接的方式获取延迟,即使用如下方法:
schedule_timeout:
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (jit_delay*Hz);
有时驱动程序需要非常短的延迟来与硬件同步。此时,使用jiffies值无法达到目的。这时就要用内核函数udelay和mdelay。u表示希腊字母“mu”(m),它代表“微”。
它们的原型如下:
#include <Linux/delay.h>
void udelay(unsigned long usecs); //软件循环延迟指定数目的微秒数
void mdelay(unsigned long msecs); //使用 udelay 做循环
该函数在绝大多数体系结构上是作为内联函数编译的。udelay函数里要用到BogoMips值:它的循环基于整数值loops_per_second,这个值是在引导阶段计算BogoMips时得到的结果。
udelay函数只能用于获取较短的时间延迟,因为loops_per_second值的精度只有8位,所以,当计算更长的延迟时会积累出相当大的误差。尽管最大能允许的延迟将近1s(因为更长的延迟就要溢出),推荐的udelay函数参数最大值是取1000ms(1ms)。当延迟大于11ms时可以使用函数mdelay。
许多驱动程序需要将任务延迟到以后处理,但又不想借助中断。Linux为此提供了三种方法:任务队列、tasklet和内核定时器






