内核代码阅读(2) - 内核中的C语言和汇编

内核中的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);
  }
上一篇:内核代码阅读(5) - do_page_fault之栈扩展


下一篇:《hotwheel 网络模型和进程模型》