天天看點

核心代碼閱讀(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);
  }      

繼續閱讀