Essential Linux Device Driver附录A . Linux汇编

By 宋宝华 / 本系列文章交流与讨论:@宋宝华Barry

设备驱动程序有时需要用汇编实现一些代码片断,因此让我们看看Linux上汇编编程的不同特性。

图A.1显示了Linux在PC兼容系统上的引导顺序,是第2章“内核一瞥”中图2.1的缩减版。图中的固件组件是用不同的汇编语法实现的:

· BIOS通常全部用汇编编写。一些流行的PC BIOS使用像Microsoft Macro Assembler (MASM)这样的汇编来编码。

· Linux 引导程序,像LILO和GRUB用C与汇编混合编写。SYSLINUX引导程序整个用Netwide Assembler(NASM)汇编编写。

· 实模式的Linux启动代码使用GNU汇编器(GAS)编码。

· 保护模式的BIOS调用用内联汇编编写。内联汇编是GCC支持的结构,在C语句之间插入汇编。

图 A.1. 固件组件与汇编语法

clip_image002

在图A.1中,上面的两个组件通常遵守基于Intel的汇编语法,而下面的两个用AR&T(或GAS)语法来编码。也有一些例外,GRUB的汇编部分就使用GAS。

为了演示这两种语法之间的差异,考虑如下输出一个字节到并口的代码。在BIOS或引导程序所使用的Intel格式中,你将会编写代码:

mov dx, 03BCh ;0x3BC is the I/O address of the parallel port

mov al, 0ABh ;0xAB is the data to be output

out dx, al ;Send data to the parallel port

然而,如果你想从Linux实模式启动代码中完成同样的工作,你将需要编写如下代码:

movw $0x3BC, %dx

movb $0xAB, %al

outb %al, %dx
你会发现,不像Intel格式,在AT&T语法中,首先出现的是源操作数,目的操作数在其后。AT&T格式中的寄存器名字由%开始,立即数用$开始。AT&T的操作码为了指定内存操作数的宽度,都带有后缀如b(针对字节)和w(针对字);而Intel语法中通过查看操作数而不是操作码来实现此目的。在Intel语法中,为了移动指针引用,你需要为操作数指定前缀,如byte ptr。

学习AT&T语法的益处是它被GAS和内联GCC所支持,而GAS和GCC不仅运行于基于Intel的系统上,也运行于各种处理器架构。

下面,让我们使用GCC内联汇编重写前面的代码片断,它是你在保护模式的内核将要用到的:

unsigned short port = 0x3BC;

unsigned char data = 0xAB;

asm("outb %%al, %%dx\n\t"

:

: "a" (data), "d" (port)

);

GCC支持的汇编格式通常如下:

asm(assembly : output operand constraints : input operand constraints : clobbered operand specifier );
在操作数项,a,b,c,d,S和D分别代表EAX,EBX,ECX,EDX,ESI和EDI寄存器。输入操作数constraint用于在执行汇编指令之前,将数据从提供的变量里拷贝至寄存器。关于GCC内联汇编语法的细节请查看GCC 内联汇编指南( www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html)。

在我们的例子中,唯一用到的constraint是针对输入操作数的。此约束有效地拷贝data的值至AL寄存器,以及port的值至DX寄存器。在内联汇编中,寄存器名由%%开始,因为%被用于指定提供的操作数。%i代表第i个操作数,因此,在前面的例子内联汇编代码片断中,如果你想指定data和port,可以分别使用%0和%1。

为了对内联汇编转换有更清晰的了解,让我们看看对应于前面的内联汇编片断、通过提供-s命令行参数给GCC,由编译器产生的汇编代码。为了理解,请阅读针对产生的每行代码的注释:

movw $956, -2(%ebp) # Value of data in stack set to 0x3BC

movb $-85, -3(%ebp) # Value of port in stack set to 0xAB

movb -3(%ebp), %al # movb 0xAB, %al

movw -2(%ebp), %dx # movw 0x3BC, %dx

#APP # Marker to note start of inline assembly

outb %al, %dx # Write to parallel port

#NO_APP # Marker to note end of inline assembly

你也可以在用户模式的程序中使用内联汇编。下面是用内联汇编编写的一个应用程序,调用syslog()系统调用以从内核的printk()的环形缓冲区中读取最后的128字节:

#define READ_COMMAND 3 /* First argument to

syslog() system call */

#define MSG_LENGTH 128 /* Third argument to syslog() */

int

main(int argc, char *argv[])

{

int syslog_command = READ_COMMAND;

int bytes_to_read = MSG_LENGTH;

int retval;

char buffer[MSG_LENGTH]; /* Second argument to syslog() */

asm volatile(

"movl %1, %%ebx\n" /* READ_COMMAND */

"movl %2, %%ecx\n" /* buffer */

"movl %3, %%edx\n" /* bytes_to_read */

"movl $103, %%eax\n" /* __NR_syslog */

"int $128\n" /* Generate System Call */

"movl %%eax, %0" /* retval */

:"=r" (retval)

:"m"(syslog_command),"r"(buffer),"m"(bytes_to_read)

:"%eax","%ebx","%ecx","%edx");

if (retval > 0) printf("%s\n", buffer);

}
正如在第4章“打下基础”中所学到的,int $128(或者int 0x80)指令产生一个软中断,陷入系统调用。由于系统调用导致从用户模式至内核模式的转换,故函数参数未传入用户或内核堆栈中,而是在CPU寄存器中。此系统调用号(在include/asm-your-arch/unistd.h中有完整列表)存储在EAX寄存器中。对于syslog()系统调用,调用号是103。如果查看syslog()的参考页,将会发现它需要三个参数:命令,存放返回数据的缓冲区的地址,以及缓冲区的长度。这些分别通过EBX、ECX和EAX来传递。返回值被从EAX传递至retval。此内联汇编调用被转换为如下语句:

retval = syslog(syslog_command, buffer, bytes_to_read);

如果你编译并运行此代码,将会看到如下从内核的环形缓冲区中获取的输出:

0:0:0:0: Attached scsi removable disk sda

<5>sd 0:0:0:0: Attached scsi generic sg0 type 0

<7>usb-storage: device scan complete

...
arch/x86/kernel/entry_32.S中的所有内核系统调用trap会保存所有的寄存器内容至堆栈,因此 ,即使用户空间的代码使用CPU寄存器来传递参数,实际上系统调用处理函数还是从堆栈中取其参数,。为了确保系统调用例程预期的参数在堆栈中,都用GCC属性asmlinkage进行标记。需要注意的是asmlinkage与asm(或__asm__)没有任何关系,后者用于声明内联汇编。

让我们通过演示一个内联汇编的例子来结束本节,此例子修改自基于PowerPC的电路板的Linux引导程序。假设此电路板上的flash存储器不支持背景操作(BackGround Operation,BGO)。这意味此引导程序代码从flash执行时,不能写入flash;但有时这是必须的,例如如果引导程序需要更新内核映象,而此映象存放于flash的另一部分。一个解决方案是修改引导程序,以便用于写入和擦除flash的引导代码完全从指令cache(I-cache)中执行,而数据段放入数据cache(D-cache)中。示例用的GCC内联汇编编写的宏用于完成将必要的引导程序指令搬入I-cache的工作。为了理解此代码片断,你需要有一定的PowerPC汇编知识:

/* instr_length is the number of instructions to touch

into I-cache. _load_i$_copy and _end_i$_copy are

program labels */

#define load_into_icache_copy(instr_length) \

asm volatile("lis %%r3, 0x1@h\n \

ori %%r3, %%r3, 0x1@l\n \

mticcr %%r3\n \

isync\n \

\n \

lis %%r6, _end_i$_copy@h\n \

ori %%r6, %%r6, _end_i$_copy@l\n \

icbt %%r0, %%r6\n \

lis %%r4, %0@h\n \

ori %%r4, %%r4, %0@l\n \

mtctr %%r4\n \

_load_i$_copy: \

addis %%r6, %%r6, 32@ha\n \

addi %%r6, %%r6, 32@l\n \

icbt %%r0, %%r6\n \

bdnz _load_i$_copy\n \

_end_i$_copy: \

nop\n" \

: \

: "i"(instr_length) \

:"%r6","%r4","%r0","r8","r9");

调试

为了调试实模式内核,不能使用我们在第21章“调试设备驱动”中所讨论、使用过的调试器,如kdb或kgdb。调试内核汇编片断的便捷方式是将代码转换为Intel类型的语法后,使用DOS调试工具。但调试器是在16位时代编写的,因此,不能调试32位的代码,例如不能调试初始化EAX寄存器的代码。从Internet上可以下载一些32位的免费调试器。第21章所讨论的JTAG调试器是万金油,因为这一工具可用于调试BIOS,引导程序,Linux实模式代码,以及内核与BIOS之间的交互。


Chapter 1, "Introduction," starts our tryst with Linux. It hurries you through downloading the kernel sources, making trivial code changes, and building a bootable kernel image. Chapter 2, "A Peek Inside the Kernel," takes a brisk peek into the innards of the Linux kernel and teaches you some must-know kernel concepts. It first takes you through the boot process and then describes kernel services particularly relevant to driver development such as kernel threads, timers, concurrency, and memory management. Chapter 3, "Getting Started with Device Drivers," gets you started with the art of writing Linux device drivers. It looks at interrupt handling, the new Linux device model, and Linux assembly. In this chapter, you'll also learn to use kernel helper interfaces such as linked lists, work queues, completion functions, and notifier chains. These helper facilities simplify your code, weed out redundancies from the kernel, and help long-term maintenance. Chapter 4, "Character Drivers," looks at the architecture of character device drivers. Several concepts introduced in this chapter such as polling, asynchronous notification, and I/O control, are relevant to subsequent chapters as well, since many device classes discussed in the rest of the book are 'super' character devices. Chapter 5, "Serial Drivers," explains the kernel layer that handles serial devices. The serial layer consists of low-level drivers, the TTY layer, and line disciplines. Chapter 6, "Input Drivers," discusses the kernel's input subsystem that is responsible for servicing devices such as keyboards, mice, and touch panels. Chapter 7, "The Inter-Integrated Circuit Protocol," dissects drivers for devices such as EEPROMs that are connected to the system I2C bus or SMBus. The chapter also looks at other serial technologies such as the SPI bus and one-wire bus. Chapter 8,"PCMCIA and Compact Flash," delves into the PCMCIA subsystem. It teaches you to write drivers for devices having a PCMCIA or Compact Flash form factor. Chapter 9, "Peripheral Component Interconnect," looks at kernel support for PCI and its derivatives such as CardBus and PCI Express. Chapter 10, "Universal Serial Bus," explores USB architecture and device drivers. Chapter 11, "Video Drivers," explains the Linux video family. Chapter 12, "Audio Drivers," describes the Linux audio family. Chapter 13, "Block Drivers," covers drivers for devices such as IDE and SCSI. It also looks at filesystem drivers. Chapter 14, "Network Interface Cards," is dedicated to network devices. You'll learn about kernel networking data structures and how to interface network drivers with protocol layers. Chapter 15, "Linux Without Wires," looks at driving different wireless technologies such as Bluetooth, Infrared, WiFi and cellular communication. Chapter 16, "Memory Technology Devices," discusses flash memory enablement. This chapter first looks at flash-based protocols and chipsets primarily used on embedded devices. It ends by examining drivers for the Firmware Hub found on desktops and laptops. Chapter 17, "Embedding Linux," steps into the world of embedded Linux. It takes you through the main firmware components of an embedded solution, such as bootloader, kernel, and device drivers. Given the soaring popularity of Linux in the embedded space, it's likely that you'll use the device driver skills that you acquire from this book, to enable embedded devices. Chapter 18, "User Mode Drivers," looks at driving different types of devices from user space. Some device drivers, especially ones that are heavy on policy and light on performance requirements, are better off residing in user land. This chapter also explains how the new ultra-scalable process scheduler improves response times of user mode drivers. Chapter 19, "More Devices and Drivers," takes a tour of a potpourri of driver families not covered thus far, such as Error Detection And Correction (EDAC), cpufreq<...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值