内核中的C语言
GNU C 的编译器gcc
新特性
从C++引入了 inline, const 为了支持64位,增加数据类型: long long int
属性描述符 attribute
asm 和 asm
由于gcc增加了一些保留字如inline,在ANSI C并非是保留字,所以在老的C代码中可能有变量名是inline,产生了冲突。 gcc允许在保留字前后加上"__"。 "__inline__" 等价 "inline" "__asm__" 等价 "asm"
同时"attribute"也是保留字
struct fool { char a; int x[z] attribute__ ((packed)); } 表示 x数组和成员a之间,不要进行对齐。让x紧挨着a。
宏的使用
宏的正确写法
正确写法: #define DUMP_WRITE(addr,nr) do { memcpy(bufp,addr,nr); bufp += nr; } while(0) 错误1: #define DUMP_WRITE(addr,nr) memcpy(bufp,addr,nr); bufp += nr; 这样是不对的 if (addr) DUMP_WRITE(addr, nr); else do_something_else(); 经过预处理后编译失败。如果把DUMP_WRITE放在else里面,则直接引起bug!!! 错误2: #define DUMP_WRITE(addr,nr) { memcpy(bufp,addr,nr); bufp += nr; } 同样编译错误
空的宏
#define prepare_to_switch() do { } while(0)
内核中的队列
list_head像一个扣子一样存在每个想要被串起来的结构中
struct list_head { struct list_head *next, *prev; }; struct page { list_head list; list_head lru; }
初始化
#define INIT_LIST_HEAD(ptr) do { \ ptr->next = ptr; \ ptr->prev = ptr; \ } while(0);
将一个新结构体挂到链上
static __inline__ void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } static __inlie__ void __list_add(struct list_head * new, struct list_head * prev, struct list_head * next) { new->next = next; new->prev = prev; next->prev = new; prev->ext = new; }
通过list_head找到宿主结构
page = memlist_entry(curr, struct page, list); #define list_entry(ptr, type, member) \ ((type *)((char*)(ptr) - (unsigned long)(&((type*)0)->member)))
内核中的汇编
汇编
纯汇编.s .S -> 预处理 -> .s 嵌入在C语言的汇编
GNU的i386汇编 AT&T
特点
1) 小写 2) 寄存器前% 3) 顺序 4) 直接数前面要加上$ 5) 操作的大小在指令的最后一个字母 b, w, l 6) jump/call 要加上*(函数指针) 7) ljmp lcall 8) 间接寻址 Section:disp(base, index, scale) 寻址数组:base + index*scale 数组的元素是一个结构体,disp是字段在结构上的偏移 -4(%ebp) = -4(%ebp, 0, 0) 0和逗号可以省略 foo(, %eax, 4) = 0 + %eax*4 - foo
嵌入在C的汇编
关键字
要嵌入汇编需要使用asm关键字 #define __SLOW_DOWN_IO __asm__ __volatitle__("outb %al, $0x80") #define __SLOW_DOWN_IO __asm__ __volatitle__("jmp 1f \n1:\tjmp 1f\n1:") jmp 1f //1f表示向前找1 1: jmp 1f 1:
寄存器的分配 和 c语言变量的绑定
static __inline__ void atomic_add(int i, atomic_t *v) { __asm__ __volatile__( LOCK "addl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); } 格式 -> 指令部: 输出部: 输入部: 损坏部 难点:如何把C中的变量 和 汇编中的操作数结合起来。因为,这段汇编的上下文寄存器分配情况只有gcc知道。 做法:只提供具体的指令部分,而对寄存器的使用则提供一个样板和一些约束条件。到底如何结合由gcc, gas处理。 指令部分中的%0, %1就是需要使用的寄存器的样板操作数,多少取决于CPU的通用寄存器个数。 由于%给了寄存器样板操作数了,所以嵌入到c中的汇编,寄存器前要加两个%。
c的变量和寄存器的约束条件 constraint
输出部中的约束
"=m" (v->counter) 1) 多个约束用逗号分开。 2) 约束以=开始 3) "=m" 表示相应的操作数(%0) 是一个内存单元,而且执行这段汇编后不保留原值(因为它在输出部麻)。
输入部中的约束
:"ir" (i), "m" (v->counter)); 1) 不带"=" 2) 有两个约束 "ir" (i), 表示汇编指令中的%1可以是在寄存器中的直接数("ir": i表示直接数(immediate), r表示寄存器),并且这个操作数来自于c语言的变量i. "m" (v->counter), 表示指令中的%2是一个内存上的单元,值来自于c语言的v-counter 根据输入和输出部的对寄存器的约束,gcc会自动分配寄存器,并且保证执行前后不会影响现场。
操作数的编号
从输出部的第一个约束还是 %0 %1 %2
约束描述
"m", "v", "o" --> 内存单元 "r" --> 任意的寄存器 "q" --> eax ebx ecx edx "a", "b", "c", "d" --> 分别表示eax ebx ecx edx "S", "D" --> ESI EDI "I" --> 常数[0~32) "g" --> 任意 "i", "h" --> 直接操作数
字符串拷贝
static inline void * __memcpy(void * to, const void * from, size_t n) { int d0, d1, d2; __asm__ __volatile__( "rep ; movsl\n\t" "testb $2,%b4\n\t" "je 1f\n\t" "movsw\n" "1:\ttestb $1,%b4\n\t" "je 2f\n\t" "movsb\n" "2:" : "=&c" (d0), "=&D" (d1), "=&S" (d2) :"0" (n/4), "q" (n),"1" ((long) to),"2" ((long) from) : "memory"); return (to); }