2010年7月11日 星期日

如何在32bits保護模式下作debug

以下我所論及的主題並非是在os的保護模式底下的debug方式;而是當你有自己的執行系統,這個執行系統是從16bits模式執行後進入32bits保護模式,並且執行自己定義的ap。就類似memtest86這種從booting到run ap都能一手掌控的代碼。然而memtest在保護模式下都是C語言代碼,因此要debug相對容易,所以我們要討論的是以assembly為架構的debug方式。
首先我們知道frame buffer是以為址0xb8000~0xb8fff為vga顯示控制區域;當然其他模式mapping到的位址也不同;但我們討論的是前者。雖然在dos或16bits模式可以使用int 10h來讀寫螢幕,我們也知道int 10h的作法也是去控制frame buffer。然而進入保護模式下傳統的bios及dos中斷已不能調用。但是我們還是可以直接去存取frame buffer;所以我寫了比下代碼以assembly的方式來顯示一些除錯用途的資訊:
以下資訊為如何在保護模式下debug,並利用健盤做單步debug
;清除所有的輸出緩衝器資料
push ax
FlushOBF: in al,64h
test al,1
jz NotOBF
in al,60h
jmp short FlushOBF
NotOBF: pop ax


push ecx
push ebx
mov ecx,edi
POS_XY bx,20,23
call sub32_hexprint
pop ebx
pop ecx

;等待8042輸出緩衝器有資料送進來
push eax
WaitOBFLoop:
in al,64h
test al,1
jz WaitOBFLoop

pop eax
;以上這小段完成健盤單步執行判斷,做法是當無按鍵被按下時,便一直執行迴圈
;用法:
;先push ecx和ebx
;將要顯示的資料存到ecx
;POS_XY bx,__x,__y
;call sub32_hexprint
;pop ecx和ebx

sub32_hexprint proc
pushad
mov eax,160
mul bl
add eax,SCREEN_ADR
xchg eax,edx
mov eax,2
mul bh
add edx,eax
mov bx,8 ;count
xor edi,edi
NEXT:
call cl2digital
mov BPTR[edx+edi],al
shr ecx,4
add edi,2
dec bx
jnz NEXT
popad
ret
sub32_hexprint endp

cl2digital proc
xor eax,eax
push ecx
and ecx,0fh
cmp cl,10
jae isA2F
add cl,30h
jmp exit
isA2F:
sub cl,10
add cl,41h
exit:
mov al,cl
pop ecx
ret
cl2digital endp

下面是marco
SCREEN_ADR equ 0b8000h
POS_XY macro r_, x_, y_
mov r_, (((x_) SHL 8) + (y_))
endm


以下為鍵盤控制指令參考:
; ========================================================
; 以下為8042界面函數之內部呼叫函數
; ========================================================
;
Flush8042 PROC
; 清除所有的輸出緩衝器資料
push ax
FlushOBF: in al,64h
test al,1
jz NotOBF
in al,60h
jmp short FlushOBF
NotOBF: pop ax
retn
Flush8042 ENDP
;
WaitIBF PROC
; 等待8042輸入緩衝器有空
WaitIBFLoop: push ax
in al,64h
test al,2
pop ax
jnz WaitIBFLoop
retn
WaitIBF ENDP
;
WaitOBF PROC
; 等待8042輸出緩衝器有資料送來
WaitOBFLoop: push ax
in al,64h
test al,1
pop ax
jz WaitOBFLoop
retn
WaitOBF ENDP
;
Read8042Data PROC
; 讀取8042回應資料
; 傳回: AL = 回應資料
call WaitOBF ; 等待資料回庄
in al,60h
retn
Read8042Data ENDP
;
Write8042Data PROC
; 送出資料給8042
; 參數: AL = 8042系統命令或資料
; 備註: 本函數亦為送出系統命令或資料的函數
call WaitIBF ; 等待輸入緩衝區有空
out 60h,al ; 送出資料
call WaitIBF ; 確認8042收到
retn
Write8042Data ENDP
;
RealSend8042Cmd PROC
; 送出一般命令碼給8042
; 參數: AL = 8042一般控制命令
; 備註: 8042命令之參數或傳回值由外界處理
call WaitIBF ; 等待輸入緩衝區有空
out 64h,al ; 送出命令
call WaitIBF ; 確認8042收到
retn
RealSend8042Cmd ENDP
;
RealSend8042Sys PROC
; 送出系統命令碼或參數給8042
; 參數: AL = 8042系統控制命令或參數
; 傳回: AL = 8042回應值
; 備註: 除回音外,其餘命令或參數應檢查是否傳回ACK
call Flush8042
call Write8042Data
call Read8042Data
retn
RealSend8042Sys ENDP
;
; ========================================================
; 以下為8042界面函數,使用前必須使用CLI將岔斷禁能
; ========================================================
;
Send8042Cmd PROC
; 送出一般命令碼給8042
; 參數: AL = 8042一般控制命令
call RealSend8042Cmd
retn
Send8042Cmd ENDP
;
Read8042Cmd PROC
; 送出讀取命令給8042
; 參數: AL = 8042讀取命令
; 傳回: AL = 讀取值
call Flush8042
call RealSend8042Cmd
call Read8042Data
retn
Read8042Cmd ENDP
;
Write8042Cmd PROC
; 送出寫入命令給8042
; 參數: AL = 8042寫入命令
; AH = 寫入資料
; 備註: AX值會被破壞
call RealSend8042Cmd
xchg al,ah
call Write8042Data
retn
Write8042Cmd ENDP
;
Echo8042 PROC
; 送出8042回音命令
; 傳回: AL = EEh
mov al,0EEh ; 回音命令
call RealSend8042Sys
retn
Echo8042 ENDP
--> 閱讀更多...

2010年7月6日 星期二

u-boot 顯示logo,並說明其原理part1


jpegtopnm Benson.jpg l ppmquant 31 l ppmtobmp -bpp 8 > Benson-8bit.bmp
紅色l是管線符號,因無法正常顯示所以用小寫L取代
以上這個指令必須先安裝netpbm,其作用是將jpeg檔轉成特定格式的bmp檔。將Benson-8bit.bmp更改檔名為denx.bmp並存到tools\logos,重新compile u-boot,便可完成,如上圖。接著我們來說明其draw logo原理:
在/tools/Makefile:
LOGO_H = $(OBJTREE)/include/bmp_logo.h
及
ifeq ($(LOGO_BMP),)
LOGO_BMP= logos/denx.bmp
endif
ifeq ($(VENDOR),atmel)
LOGO_BMP= logos/atmel.bmp
endif
及
$(LOGO_H):$(obj)bmp_logo $(LOGO_BMP)
$(obj)./bmp_logo $(LOGO_BMP) >$@ #這個$@就是bmp_logo.h
也就是說$(LOGO_H)這個目標的必要條件是bmp_logo 及denx.bmp這兩個檔案,才能產生bmp_logo.h這個主角,它就是denx.bmp的位元圖陣列。
所以在uboot主目錄中的makefile中有這麼一行:@rm -f $(obj)include/bmp_logo.h。在clean 時會將其刪除。
以上資訊若使用Source Insight根本無法搜尋得到,終就還是得到linux使用gerp。像這次我的搜尋指令如下:grep -Hwn "bmp_logo.h" -r ./ 。接著我們來了解一下bmp_logo.c:這是一支單一檔案所完成的獨立執行檔;我先把代碼列出;再來加入註解;另外也必須了解點陣圖的格式,請參考點陣圖(Bitmap)檔案格式
#include (stdio.h)
#include (stdlib.h)
#if defined(__linux__)
#include (stdint.h)
#else
#ifdef __CYGWIN__
#include "elf.h"
#else
#include (inttypes.h)
#endif
#endif

typedef struct bitmap_s { /* bitmap description */
uint16_t width;
uint16_t height;
uint8_t palette[256*3]; //調色盤資料
uint8_t *data; //圖的bit_map資料指標
} bitmap_t;

#define DEFAULT_CMAP_SIZE 16 /* size of default color map */

/* Neutralize little endians. */
uint16_t le_short(uint16_t x) //因為檔案格式是依照LITTLE ENDIAN排列,因此要找出實際數值必須作移位。
{
uint16_t val;
uint8_t *p = (uint8_t *)(&x);
val = (*p++ & 0xff) 左移 0;
val = (*p & 0xff) 左移 8;
return val;
}
void skip_bytes (FILE *fp, int n)
{
while (n-- > 0) fgetc (fp);
}

int main (int argc, char *argv[])
{
int i, x;
FILE *fp;
bitmap_t bmp;
bitmap_t *b = &bmp;
uint16_t data_offset, n_colors;

if (argc < color="#cc33cc">//判斷參數少於2便顯示fail
fprintf (stderr, "Usage: %s file\n", argv[0]);
exit (EXIT_FAILURE);
}
if ((fp = fopen (argv[1], "rb")) == NULL) {//開啟檔案,若失敗顯示fail
perror (argv[1]);
exit (EXIT_FAILURE);
}
if (fgetc (fp) != 'B' fgetc (fp) != 'M') { //判斷檔頭前2個byte是否為"BM",否則顯示錯誤
fprintf (stderr, "%s is not a bitmap file.\n", argv[1]);
exit (EXIT_FAILURE);
}
/*
* read width and height of the image, and the number of colors used;
* ignore the rest
*/
skip_bytes (fp, 8);
fread (&data_offset, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 6);
fread (&b->width, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 2);
fread (&b->height, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 22);
fread (&n_colors, sizeof (uint16_t), 1, fp);
skip_bytes (fp, 6);
/*
* Repair endianess. 求出實際數值,包括data_offset b->width b->height n_colors
*/
data_offset = le_short(data_offset);
b->width = le_short(b->width);
b->height = le_short(b->height);
n_colors = le_short(n_colors);

/* assume we are working with an 8-bit file */
if ((n_colors == 0) or (n_colors > 256 - DEFAULT_CMAP_SIZE)) {
/* reserve DEFAULT_CMAP_SIZE color map entries for default map */
n_colors = 256 - DEFAULT_CMAP_SIZE;
}
//使用printf便可印到bmp_logo.h那是因為$(obj)./bmp_logo $(LOGO_BMP) >$@ 。 這個">"轉向指令。
以下的代碼就不用再加註解,只要比對一下產生出來的bmp_logo.h,就知道了
printf ("/*\n"
" * Automatically generated by \"tools/bmp_logo\"\n"
" *\n"
" * DO NOT EDIT\n"
" *\n"
" */\n\n\n"
"#ifndef __BMP_LOGO_H__\n"
"#define __BMP_LOGO_H__\n\n"
"#define BMP_LOGO_WIDTH\t\t%d\n"
"#define BMP_LOGO_HEIGHT\t\t%d\n"
"#define BMP_LOGO_COLORS\t\t%d\n"
"#define BMP_LOGO_OFFSET\t\t%d\n"
"\n",
b->width, b->height, n_colors,
DEFAULT_CMAP_SIZE);

/* allocate memory */
if ((b->data = (uint8_t *)malloc(b->width * b->height)) == NULL) {
fclose (fp);
printf ("Error allocating memory for file %s.\n", argv[1]);
exit (EXIT_FAILURE);
}

/* read and print the palette information */
printf ("unsigned short bmp_logo_palette[] = {\n");

for (i=0; ipalette[(int)(i*3+2)] = fgetc(fp);
b->palette[(int)(i*3+1)] = fgetc(fp);
b->palette[(int)(i*3+0)] = fgetc(fp);
x=fgetc(fp);

printf ("%s0x0%X%X%X,%s",
((i%8) == 0) ? "\t" : " ",
(b->palette[(int)(i*3+0)] >> 4) & 0x0F,
(b->palette[(int)(i*3+1)] >> 4) & 0x0F,
(b->palette[(int)(i*3+2)] >> 4) & 0x0F,
((i%8) == 7) ? "\n" : ""
);
}

/* seek to offset indicated by file header */
fseek(fp, (long)data_offset, SEEK_SET);

/* read the bitmap; leave room for default color map */
printf ("\n");
printf ("};\n");
printf ("\n");
printf ("unsigned char bmp_logo_bitmap[] = {\n");
for (i=(b->height-1)*b->width; i>=0; i-=b->width) {
for (x = 0; x <>width; x++) {
b->data[(uint16_t) i + x] = (uint8_t) fgetc (fp) + DEFAULT_CMAP_SIZE;
}
}
fclose (fp);

for (i=0; i<(b->height*b->width); ++i) {
if ((i%8) == 0)
putchar ('\t');
printf ("0x%02X,%c", b->data[i], ((i%8) == 7) ? '\n' : ' ');
}
printf ("\n"
"};\n\n"
"#endif /* __BMP_LOGO_H__ */\n"
);

return (0);
}
--> 閱讀更多...

2010年6月29日 星期二

memtest86關於smp實作

As we know memtest86+ is to originate from memtest86.但是memtest86 3.5裡頭關於SMP的源碼,在memtest86+ 4.0及4.1獲得延續。4.0及4.1雖然從代碼仍可看出SMP足跡,但實際上已經沒有支援SMP了。因此我們若要查看SMP的完整實作,必需Download memtest86 3.5 source code。
在main.c/test_start/initialise_cpus();首先要支援SMP就必需先調用這個routine,此處的test_start相當於memtest86+版的do_test。
void initialise_cpus(void)
{
int cpu_num, j;
smp_init_bsp();
num_cpus = smp_num_cpus();
/* let the BSP initialise the APs. */
for(cpu_num = 1; cpu_num < color="#3333ff">smp_boot_ap(cpu_num);
if(!smp_mode) {
/* some error in booting the AP,
* halt the already started APs */
for (j = 1; j <=cpu_num; j++) { jmp_address[j] = (void *)ap_halt; } break; } } } smp_init_bsp要看懂這個routine要先查詢一些資訊:小木偶裡頭有關cpuid的指令說明;Intel64 and IA-32 Architectures Software Developer's Manual-3A.pdf第8章。主要是找出CPU的核心數及尋找mp_config_table表。
smp_boot_ap這個routine才是重點。
以下資料取自於http://blog.chinaunix.net/u1/41699/showart_537820.html
多核初始化和啟動過程
多核初始化協議定義了兩類處理器:bootstrap processor(BSP) 和 application processors (APs). 上電或者MP重定後,系統硬體動態選擇一個作為BSP,其餘的作為APs。BSP的IA32_APIC_BASE中的BSP flag將被置位,其他的處理器中該位將被清空.上電或者復位後,AP等待BSP的SIPI(startup signal)信號,接收到SIPI信號後,AP執行BIOS中的AP配置代碼,然後進入halt狀態。對於支援Hyper_Threading的處理器,每個邏輯處理器將被作為一個單獨的處理器對待,都會有一個唯一的APIC ID。
志強(xeon)處理器MP初始化協定演算法:
1)基於系統拓撲結構,每個邏輯處理器將分配一個8位的APIC ID.這個ID將被寫入到每個處理器的local APIC ID寄存器中.
2)BSP被確定後,BSP創建一個ACPI的table和一個MP的table並將自己的初始APIC ID加到這些表中
3)在boot-strap流程的最後,BSP設置處理器數目為1,並開始廣播SIPI. 這裏,SIPI包含BIOS AP初始化代碼的向量
4)AP收到SIPI後,第一個得到信號量的AP開始執行初始化代碼,將自己的APIC ID加到ACPI和MP的表裏面,並將處理器數目加1.完成初始化後,AP執行一個CLI指令並halt自己
5)所有的AP都初始化完後,BSP得到一個系統處理器的個數,完成boot-strap代碼,進入OS.
6)此時AP保持halt狀態,等待INITs, NMIs和SMIs.
就我所了解以上的部分應該是BIOS所完成的,參考:http://www.biosren.com/thread-1395-1-1.html

BSP初始化過程
1. 初始化memory
2. Load microcode到處理器
3. 初始化記憶體範圍寄存器(MTRRs)
4. enable cache
5. 確定BSP是否是"GenuineIntel"
6. 執行CPUID,保存CPU資訊為將來使用
7. load AP的啟動代碼到低1M的一個4K的頁裏
8. 切換到保護模式
9. 轉換4K的頁基址為一個8位的向量. 例如 0x000BD000H --> 0xBDH
10.設置APIC的SVR的bit8來enable local APIC
11.建立錯誤處理handler
12.初始化鎖信號量
13.探測系統中的AP, 方法如下:
- 設置處理器COUNT為1
- 啟動一個timer,BSP開始等待
- 此時AP開始初始化,並將COUNT加1
- timer到期,BSP檢查COUNT,如果沒有增加,就表示系統中沒有AP.
14. 等timer中斷,檢查COUNT並建立處理器數目
AP初始化
1. 獲取信號量,開始初始化
2. load microcode到處理器
3. 初始化記憶體範圍寄存器(MTRRs)
4. enable cache
5. 檢查AP是否是"GenuineIntel"
6. 保存CPUID資訊,為將來使用
7. 切換到保護模式
8. 配置AP的共存記憶體介面執行環境
9. 將處理器個數加1
10.釋放信號量
11. 執行CLI並且進入halt狀態
12.等待INIT IPI

了解以上步驟變可從代碼來查看如何實現. update中....
--> 閱讀更多...

2010年6月16日 星期三

從start_armboot routine來了解u-boot--PART2

接續上回談到env_init,列出相關代碼:注意,這裡的代碼分散在不同的檔案
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;


#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
# define ENV_HEADER_SIZE (sizeof(uint32_t) + 1)
#else
# define ENV_HEADER_SIZE (sizeof(uint32_t))
#endif


#ifdef ENV_IS_EMBEDDED //這個marco未定義
extern uchar environment[];
env_t *env_ptr = (env_t *)(&environment[0]); //因此以上兩行未執行
#else /* ! ENV_IS_EMBEDDED */
env_t *env_ptr = 0;
#endif /* ENV_IS_EMBEDDED */


#define local static
#define ZEXPORT


#ifdef DYNAMIC_CRC_TABLE //在u-boot的code裡並未定義這個MARCO;所以並不會動態產生CRC_TABLE
local int crc_table_empty = 1;
local uint32_t crc_table[256];
local void make_crc_table OF((void));


local void make_crc_table()
{
uint32_t c;
int n, k;
uLong poly; /* polynomial exclusive-or pattern */
/* terms of polynomial defining this crc (except x^32): */
static const Byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26};

/* make exclusive-or pattern from polynomial (0xedb88320L) */
poly = 0L;
for (n = 0; n < n =" 0;" c =" (uLong)n;" k =" 0;" c =" c">> 1) : c >> 1;
crc_table[n] = c;
}
crc_table_empty = 0;
}
#else
/* ========================================================================
* Table of CRC-32's of all single-byte values (made by make_crc_table)
*/
local const uint32_t crc_table[256] = { //這個crc_table會被配置
0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
0x2d02ef8dL
};
#endif /*DYNAMIC_CRC_TABLE */


#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif


#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);
#define DO2(buf) DO1(buf); DO1(buf);
#define DO4(buf) DO2(buf); DO2(buf);
#define DO8(buf) DO4(buf); DO4(buf);

//以下這個routine在lib_generic/crc32.c

uint32_t ZEXPORT crc32 (uint32_t crc, const Bytef *buf, uInt len)
{
#ifdef DYNAMIC_CRC_TABLE
if (crc_table_empty) make_crc_table(); //這行不會被執行
#endif
crc = crc ^ 0xffffffffL;
while (len >= 8)
{
DO8(buf);
len -= 8;
}
if (len) do {
DO1(buf);
} while (--len);
return crc ^ 0xffffffffL;
}


先說明關於CRC,以CRC-16為例來說明其生成過程:
CRC-16碼由兩個位元組構成,在開始時CRC寄存器的每一位都預置為1,然後把CRC寄存器與8-bit的資料進行異或(異或:二進位運算相同為0,不同為1;0^0=0;0^1=1;1^0=1;1^1=0),之後對CRC寄存器從高到低進行移位,在最高位(MSB)的位置補零,而最低位(LSB,移位後已經被移出CRC寄存器)如果為1,則把寄存器與預定義的多項式碼進行異或,否則如果LSB為零,則無需進行異或。重複上述的由高至低的移位8次,第一個8-bit資料處理完畢,用此時CRC寄存器的值與下一個8-bit資料異或並進行如前一個資料似的8次移位元。所有的字元處理完成後CRC寄存器內的值即為最終的CRC值。
下面為CRC的計算過程:
来源:(http://blog.sina.com.cn/s/blog_639260ff0100g1ms.html) - 循环冗余校验CRC_Striker_新 1.設置CRC寄存器,並給其賦值FFFF(hex)。
2.將資料的第一個8-bit字元與16位元CRC寄存器的低8位進行異或,並把結果存入CRC寄存器。
3.CRC寄存器向右移一位,MSB補零,移出並檢查LSB。
4.如果LSB為0,重複第三步;若LSB為1,CRC寄存器與多項式碼相異或。
5.重複第3與第4步直到8次移位元全部完成。此時一個8-bit資料處理完畢。
6.重複第2至第5步直到所有資料全部處理完成。
7.最終CRC寄存器的內容即為CRC值。
常用的CRC迴圈冗餘校驗標準多項式如下:
CRC(16位) = X16+X15+X2+1
CRC(CCITT) = X16+X12 +X5+1
CRC(32位) = X32+X26+X23+X16+X12+X11+X10+ X8+X7+X5+X4+X2+X+1

--> 閱讀更多...

2010年6月14日 星期一

從start_armboot routine來了解u-boot--PART1

有關這方面ㄉ相關資訊參考:http://blog.chinaunix.net/u2/70445/showart_1852111.html
http://6xudonghai.blog.163.com/blog/static/33640629200911364735696/
http://www.63da.com/?All1-uboot%D4%B4%C2%EB%B7%D6%CE%F6/
(1):typedef int (init_fnc_t) (void);
(2):init_fnc_t *init_sequence[] ={
cpu_init, /* basic cpu dependent setup */ (1)
board_init, /* basic board dependent setup */(2)
interrupt_init, /* set up exceptions */ (3)
env_init, /* initialize environment */ (4)
init_baudrate, /* initialze baudrate settings */(5)
serial_init, /* serial communications setup */ (6)
console_init_f, /* stage 1 init of console */ (7)
display_banner, /* say that we are here */ (8)
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */(9)
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */ (10)
#endif
#if defined(CONFIG_HARD_I2C) defined(CONFIG_SOFT_I2C)
init_func_i2c, (11)
#endif
dram_init, /* configure available RAM banks */ (12)
display_dram_config, (13)
NULL,
};

(3):init_fnc_t **init_fnc_ptr;

(4)for (init_fnc_ptr = init_sequence ; *init_fnc_ptr ; ++init_fnc_ptr){
if ((*init_fnc_ptr)() != 0)
{hang (); }
}
以上四段code分散在board.c中,但是它們息息相關,而最下面的for迴圈更是高竿的程式寫法:以下我們就先針對這四段code了解其如何對硬體層面作完整的初始化。
我想大家對c語言有了解的應該都知道int main(int argc,char**argv){ };其實也可以這樣寫int main(int argc,char*argv[]){ };當然以下這樣也行int main(int argc,char argv[][]){ }; 所以上面的code不言自明,說穿了也是函式指標的應用。
剛才講到for迴圈,它主要的工作是硬體週邊初始化;和硬體週邊相關的代碼必需查datasheet,才能了解其實作,前三個:cpu_init、board_init、interrupt_init我認為較簡單,而且代碼上都有註解。其他部份我想未來有談到再補充。
在此我認為較難懂的是env_init,因為實際上在u-boot中這個routine就有9個之多,所以我在上面貼了一個參考連結,專門講述這個routine。雖然裡頭並未說明---實際呼叫的是env_nand.c或是env_dataflash.c或是env_flash.c...中的env_init();實際查看在common/makefile如下:
# environment
COBJS-y += env_common.o
COBJS-$(CONFIG_ENV_IS_IN_DATAFLASH) += env_dataflash.o
COBJS-$(CONFIG_ENV_IS_IN_EEPROM) += env_eeprom.o
COBJS-y += env_embedded.o
COBJS-$(CONFIG_ENV_IS_IN_FLASH) += env_flash.o
COBJS-$(CONFIG_ENV_IS_IN_NAND) += env_nand.o
COBJS-$(CONFIG_ENV_IS_IN_NVRAM) += env_nvram.o
COBJS-$(CONFIG_ENV_IS_IN_ONENAND) += env_onenand.o
COBJS-$(CONFIG_ENV_IS_IN_SPI_FLASH) += env_sf.o
COBJS-$(CONFIG_ENV_IS_NOWHERE) += env_nowhere.o
然後再查看include\configs\smdk2410.h如下:
#define CONFIG_ENV_IS_IN_FLASH 1
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
●所以在此會被編譯的只有env_flash.c。
然而在tekkaman所改版的部分它將smdk2410.h重新命名為mini2440.h,並修改其中內容如下:
//#define CONFIG_ENV_IS_IN_EEPROM 1/* use EEPROM for environment vars */
//#define CONFIG_ENV_OFFSET 0x000 /* environment starts at offset 0 */
//#define CONFIG_ENV_SIZE 0x400 /* 1KB */
#define CONFIG_ENV_IS_IN_NAND 1
//#define CONFIG_ENV_IS_IN_FLASH 1 //注意此行被mark掉了
#define CONFIG_ENV_OFFSET 0X60000
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
所以在此會被編譯的是env_nand.c。

另外題外話:最近使用arm-linux-objdump -D u-boot >u-boot.asm這個指令將u-boot反組譯後有助於trace參考之方便性。
--> 閱讀更多...

2010年6月10日 星期四

S3C2440與NAND FLASHK9F1208的接線分析

圖一
圖二
圖三
圖四
圖五
本文來自: 高校自動化網(http://www.zdh1909.com/) 詳細出處參考(轉載請保留本鏈結):http://www.zdh1909.com/html/MCS51/8883.html
NAND FLASH的接線方式和NOR FLASH,SDRAM都不一樣。以TQ2440開發板用的K9F1208為例,分析NAND FLASH的接線方式。K9F1208結構如圖一所示:
K9F1208位寬是8bit。
一頁: 512byte + 16byte 最後16byte是用於存儲校驗碼和其他資訊用的,不能存放實際的資料。
一個塊有32 page:(16k+512)byte
K9F1208有4096個塊:(64M+2M)byte,總共有64Mbyte可操作的晶片容量
NAND FLASH以頁為單位讀寫資料,以塊為單位擦除數據。
S3C24440和K9F1208的接線如圖二:
圖三是S3C2440的NAND FLASH引腳配置:
當選定一個NAND FLASH的型號後,要根據選定的NAND FLASH來確定S3C2440的NCON,GPG13,GPG14,GPG15的狀態。
圖四是S3C2440中4個腳位元狀態的定義:
K9F1208的一頁是512byte,所以NCON接低電平,GPG13接高電平。
K9F1208需要4個定址命令,所以GPG14接高電平K9F1208的位寬是8,所以GPG15接低電平。
NAND FLASH定址對K9F1208來說,地址和命令只能在I/O[7:0]上傳遞,資料寬度是8位元。
地址傳遞分為4步,如圖五
:
第1步發送列位址,既選中一頁512BYTE中的一個位元組。512byte需要9bit來選擇,這裏只用了A0-A7,原因是把一頁分成了2部分,每部分256位元組。通過發送的讀命令字來確定是讀的前256位元組還是後256位元組。
當要讀取的起始地址(Column Address)在0~255內時我們用00h命令,當讀取的起始位址是在256~511時,則使用01h命令。
一個塊有32頁,用A9-A13共5位來選擇一個塊中的某個頁。
總共有4096個塊,用A14-A25共12位來選擇一個塊。
K9F1208總共有64Mbyte,需要A0-A25共26個位址位。
例如要讀NAND FLASH的第5000位元組開始的內容。把5000分解成列地址和行地址。
Column_address = 5000%512 = 392
Page_address = (5000>>9) = 9
因為column_address>255,所以用01h命令讀
發送命令和參數的順序是:
NFCMMD = 0x01;從後256位元組開始讀
NFADDR = column_address & 0xff;取column_address的低8位元送到數據線
NFADDR = page_address & 0xff;發送A9-A16
NFADDR = (page_address >>8) & 0xff; 發送A17-A24
NFADDR = (page_address >> 16) & 0xff;發送A25
上面的NFCMMD,NFADDR.是S3C2440的NAND FLASH控制寄存器。讀取的數據會放在NFDATA中。
--> 閱讀更多...

2010年5月24日 星期一

函式指標的用法

函式指標的應用,可減少判斷的次數;把函式當成參數傳遞。此處轉貼一個學c的連結:C程式語言教學
方法一:
ulong FUNC(ulong param1, ulong param2, ulong param3){
........
........
return retval;
}
void Call_FUNC(int paramX, void* paramY){
........
........
return;
}
void Main_FUNC(){
........
Call_FUNC( x , (void*)FUNC);
........
return;
}
方法二:
struct STRUCTNAME{
int A;
unsigned B;
char *NAME;
long (*FUNC_X)(void);
void (*FUNC_Y)(int,long);
};
static long ptr1_func(void){
......
}
static void ptr2_func(int i, long k){
......
}
static long ptr3_func(void){
......
}
static void ptr4_func(int a, long b){
......
}
static long ptr5_func(void){
......
}
static void ptr6_func(int v, long u){
......
}
static long ptr7_func(void){
......
}
static void ptr8_func(int n, long f){
......
}
static struct STRUCTNAME struct_array[] = {
{ x, x , "xxxx", ptr1_func , ptr2_func(x,y)},
{ x, x , "xxxx", ptr3_func , ptr4_func(d,e)},
{ x, x , "xxxx", ptr5_func , ptr6_func(f,g)},
{ x, x , "xxxx", ptr7_func , ptr2_func(j,k)},
{ x, x , "xxxx", ptr3_func , ptr8_func(r,t)}
};
函式指標呼叫如下
struct_array[index].FUNC_X();
struct_array[index].FUNC_Y(intN,longM);
以下轉貼自程式設計俱樂部http://www.programmer-club.com.tw/ShowSameTitleN/c/33283.html

最近在trace code看到一個函式指標的觀念想請教一下,我寫了一個範例code如下
#include
#include
int fun(int, int);
typedef int(*fun_one)(int, int); //書上大部分介紹函式指標的寫法
typedef int(fun_two)(int, int); //這是在trace linux kernel code看到的寫法
int test1(fun_one);
int test2(fun_two *);
int main(void){
test1(fun);

test2(fun);

return 0;
}

int fun(int x, int y){
printf("x : %d, y : %d\n", x, y);
}
int test1(fun_one f1)//這邊不用多加一個指標(*)的符號
{f1(1,1);}
int test2(fun_two *f2)//要宣告f2是指向fun_two的指標
{f2(2,2);}
這邊兩種方式都可以run。
--> 閱讀更多...

2010年5月6日 星期四

linux SMP 啟動過程學習筆記

SMP 硬體體系結構:
對於 SMP 最簡單可以理解為系統存在多個完全相同的 CPU ,所有 CPU 共用匯流排,擁有自己的寄存器。對於記憶體和外部設備訪問,由於共用匯流排,所以是共用的。 Linux 作業系統多個 CPU 共用在系統空間上映射相同,是完全對等的。
由於系統中存在多個 CPU ,這是就引入一個問題,當外部設備產生中斷的時候,具體有哪一個 CPU 進行處理?
為此, intel 公司提出了 IO APCI 和 LOCAL APCI 的體系結構。
IO APIC 連接各個外部設備,並可以設置分發類型,根據設定的分發類型,中斷信號發送的對應 CPU 的 LOCAL APIC 上。
LOCAL APIC 負責本地 CPU 的中斷處理, LOCAL APIC 不僅可以接受 IO APIC 的中斷,也需要處理本地 CPU 產生的異常。同時 LOCAL APIC 還提供了一個計時器。
如何確定那個 CPU 是引導 CPU ?
根據 intel 公司中的資料,系統上電後,會根據 MP Initialization Protocol 隨機選擇一個 CPU 作為 BSP ,只有 BSP 會運行 BIOS 程式,其他 AP 都進入等待狀態, BSP 發送 IPI 中斷觸發後才可以運行。具體的 MP Initialization Protocol 細節,可以參考 Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1 第 8 章。
引導 CPU 如何控制其他 CPU 開始運行?
BSP 可以通過 IPI 消息控制 AP 從指定的起始位址運行。 CPU 中集成的 LOCAL APIC 提供了這個功能。可以通過寫 LOCAL APIC 中提供的相關寄存器,發送 IPI 消息到指定的 CPU 上。
如何獲取系統硬體 CPU 資訊的?
在系統初始化後,硬體會在記憶體的規定位置提供關於 CPU ,匯流排 , IO APIC 等的資訊,即 SMP MP table 。在 linux 初始化的過程,會讀取該位置,獲取系統相關的硬體資訊。
2. linux SMP 啟動過程流程簡介
setup_arch()
setup_memory();
reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
find_smp_config(); // 查找 smp mp table 的位置
smp_alloc_memory();
trampoline_base = (void *) alloc_bootmem_low_pages(PAGE_SIZE); // 分配 trampoline ,用於啟動 AP 的引導代碼。
get_smp_config(); // 根據 smp mp table ,獲取具體的硬體資訊
trap_init()
init_apic_mappings();
mem_init();
zap_low_mappings(); 如果沒有定義 SMP 的話,清楚用戶空間的位址映射。
rest_init();
kernel_thread(init, NULL, CLONE_FS CLONE_SIGHAND);
init();
set_cpus_allowed(current, CPU_MASK_ALL);
smp_prepare_cpus(max_cpus);
smp_boot_cpus(max_cpus);
connect_bsp_APIC();
setup_local_APIC(); // 初始化 BSP 的 LOCAL APCI 。
map_cpu_to_logical_apicid();
針對每個 CPU 調用 do_boot_cpu(apicid, cpu)
smp_init(); // 每個 CPU 開始進行調度
trampoline.S AP 引導代碼,為 16 進制代碼,啟用保護模式
head.s 為 AP 創建分頁管理
initialize_secondary 根據之前 fork 創建設置的資訊,跳轉到 start_secondary 處
start_secondary 判斷 BSP 是否啟動,如果啟動 AP 進行任務調度。
3. 代碼學習總結
find_smp_config(); ,查找 MP table 在記憶體中的位置。具體協定可以參考 MP 協定的第 4 章。
這個表的作用在於描述系統 CPU ,匯流排, IO APIC 等的硬體資訊。
相關的兩個總體變數: smp_found_config 是否找到 SMP MP table , mpf_found SMP MP table 的線性位址。
smp_alloc_memory() 為啟動 AP 的啟動程式分配記憶體空間。相關總體變數 trampoline_base ,分配的啟動位址的線性位址。
get_smp_config() 根據 MP table 中提供的內容,獲取硬體的資訊。
init_apic_mappings(); 獲取 IO APIC 和 LOCAL APIC 的映射位址 。
zap_low_mappings(); 如果沒有定義 SMP 的話,清楚用戶空間的位址映射。將 swapper_pg_dir 中表項清零。
setup_local_APIC(); 初始化 BSP 的 LOCAL APCI 。
do_boot_cpu(apicid, cpu)
idle = alloc_idle_task(cpu);
task = copy_process(CLONE_VM, 0, idle_regs(&regs), 0, NULL, NULL, 0);
init_idle(task, cpu);
將 init 進程使用 copy_process 複製,並且調用 init_idle 函數,設置可以運行的 CPU 。
idle->thread.eip = (unsigned long) start_secondary;
修改 task_struct 中的 thread.eip ,使得 AP 初始化完成後,就運行 start_secondary 函數。
start_eip = setup_trampoline();
調用 setup_trampoline() 函數,複製 trampoline_data 到 trampoline_end 之間的代碼到 trampoline_base 處, trampoline_base 就是之前在 setup_arch 處申請的記憶體。 start_eip 返回值是 trampoline_base 對應的物理位址。
smpboot_setup_warm_reset_vector(start_eip); 設置記憶體 40:67h 處為 start_eip 為啟動地址。
wakeup_secondary_cpu(apicid, start_eip); 在這個函數中通過操作 APIC_ICR 寄存器, BSP 向目標 AP 發送 IPI 消息,觸發目標 AP 從 start_eip 位址處,從實模式開始運行。
trampoline.S
ENTRY(trampoline_data)
r_base = .
wbinvd # Needed for NUMA-Q should be harmless for others
mov %cs, %ax # Code and data in the same place
mov %ax, %ds
cli # We should be safe anyway
movl $0xA5A5A5A5, trampoline_data - r_base
這個是設置標識,以便 BSP 知道 AP 運行到這裏了。

lidtl boot_idt - r_base # load idt with 0, 0
lgdtl boot_gdt - r_base # load gdt with whatever is appropriate
載入 ldt 和 gdt
xor %ax, %ax
inc %ax # protected mode (PE) bit
lmsw %ax # into protected mode
# flush prefetch and jump to startup_32_smp in arch/i386/kernel/head.S
ljmpl $__BOOT_CS, $(startup_32_smp-__PAGE_OFFSET)
啟動保護模式,跳轉到 startup_32_smp 處
# These need to be in the same 64K segment as the above;
# hence we don't use the boot_gdt_descr defined in head.S
boot_gdt:
.word __BOOT_DS + 7 # gdt limit
.long boot_gdt_table-__PAGE_OFFSET # gdt base
boot_idt:
.word 0 # idt limit = 0
.long 0 # idt base = 0L
.globl trampoline_end
trampoline_end:
在這段代碼中,設置標識,以便 BSP 知道該 AP 已經運行到這段代碼,載入 GDT 和 LDT 表基址。
然後啟動保護模式,跳轉到 startup_32_smp 處。
Head.s 部分代碼:
ENTRY(startup_32_smp)
cld
movl $(__BOOT_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
xorl %ebx,%ebx
incl %ebx
如果是 AP 的話,將 bx 設置為 1
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */
啟用分頁,
lss stack_start,%esp
使 esp 執行 fork 創建的進程內核堆疊部分,以便後續跳轉到 start_secondary
#ifdef CONFIG_SMP
movb ready, %cl
movb $1, ready
cmpb $0,%cl
je 1f # the first CPU calls start_kernel
# all other CPUs call initialize_secondary
call initialize_secondary
jmp L6
1:
#endif /* CONFIG_SMP */
call start_kernel
如果是 AP 啟動的話,就調用 initialize_secondary 函數。
void __devinit initialize_secondary(void)
{
/*
* We don't actually need to load the full TSS,
* basically just the stack pointer and the eip.
*/
asm volatile(
"movl %0,%%esp\n\t"
"jmp *%1"
:
:"r" (current->thread.esp),"r" (current->thread.eip));
}
設置堆疊為 fork 創建時的堆疊, ip 為 fork 時的 ip ,這樣就跳轉的了 start_secondary 。
start_secondary 函數中處理如下:
while (!cpu_isset(smp_processor_id(), smp_commenced_mask))
rep_nop();
進行 smp_commenced_mask 判斷,是否啟動 AP 運行。 smp_commenced_mask 在 smp_init() 中設置。
cpu_idle();
如果啟動了,調用 cpu_idle 進行任務調度。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/jemmy858585/archive/2009/09/01/4509375.aspx
--> 閱讀更多...

2010年5月4日 星期二

【轉】終生受用的十大經典理論

【轉】終生受用的十大經典理論
1、彼得原理
  每個組織都是由各種不同的職位、等級或階層的排列所組成,每個人都隸屬於其中的某個等級。彼得原理是美國學者勞倫斯·彼得在對組織中人員晉升的相關現象研究後,得出一個結論:在各種組織中,雇員總是趨向于晉升到其不稱職的地位。彼得原理有時也被稱為向上爬的原理。這種現象在現實生活中無處不在:一名稱職的教授被提升為大學校長後,卻無法勝任;一個優秀的運動員被提升為主管體育的官員,而無所作為。對一個組織而言,一旦相當部分人員被推到其不稱職的級別,就會造成組織的人浮於事,效率低下,導致平庸者出人頭地,發展停滯。因此,這就要求改變單純的根據貢獻決定晉升的企業員工晉升機制,不能因某人在某個崗位上幹得很出色,就推斷此人一定能夠勝任更高一級的職務。將一名職工晉升到一個無法很好發揮才能的崗位,不僅不是對本人的獎勵,反而使其無法很好發揮才能,也給企業帶來損失。
2、酒與污水定律
  酒與污水定律是指把一匙酒倒進一桶污水,得到的是一桶污水;如果把一匙污水倒進一桶酒,得到的還是一桶污水。在任何組織裏,幾乎都存在幾個難弄的人物,他們存在的目的似乎就是為了把事情搞糟。最糟糕的是,他們像果箱裏的爛蘋果,如果不及時處理,它會迅速傳染,把果箱裏其他蘋果也弄爛。爛蘋果的可怕之處,在於它那驚人的破壞力。一個正直能幹的人進入一個混亂的部門可能會被吞沒,而一個無德無才者能很快將一個高效的部門變成一盤散沙。組織系統往往是脆弱的,是建立在相互理解、妥協和容忍的基礎上的,很容易被侵害、被毒化。破壞者能力非凡的另一個重要原因在於,破壞總比建設容易。一個能工巧匠花費時日精心製作的陶瓷器,一頭驢子一秒鐘就能毀壞掉。如果一個組織裏有這樣的一頭驢子,即使擁有再多的能工巧匠,也不會有多少像樣的工作成果。如果你的組織裏有這樣的一頭驢子,你應該馬上把它清除掉,如果你無力這樣做,就應該把它拴起來。
3、木桶定律
  水桶定律是講一隻水桶能裝多少水,這完全取決於它最短的那塊木板。這就是說任何一個組織,可能面臨的一個共同問題,即構成組織的各個部分往往是優劣不齊的,而劣勢部分往往決定整個組織的水準。水桶定律與酒與污水定律不同,後者討論的是組織中的破壞力量,最短的木板卻是組織中有用的一個部分,只不過比其他部分差一些,你不能把它們當成爛蘋果扔掉。強弱只是相對而言的,無法消除,問題在於你容忍這種弱點到什麼程度,如果嚴重到成為阻礙工作的瓶頸,你就不得不有所動作。
4、馬太效應
  《新約·馬太福音》中有這樣一個故事:一個國王遠行前,交給3個僕人每人一錠銀子,吩咐道:你們去做生意,等我回來時,再來見我。國王回來時,第一個僕人說:主人,你交給我的一錠銀子,我已賺了10錠。於是,國王獎勵他10座城邑。第二個僕人報告:主人,你給我的一錠銀子,我已賺了5錠。於是,國王獎勵他5座城邑。第三僕人報告說:主人,你給我的1錠銀子,我一直包在手帕裏,怕丟失,一直沒有拿出來。於是,國王命令將第三個僕人的1錠銀子賞給第一個僕人,說:凡是少的,就連他所有的,也要奪過來。凡是多的,還要給他,叫他多多益善,這就是馬太效應,反應當今社會中存在的一個普遍現象,即贏家通吃。對企業經營發展而言,馬太效應告訴我們,要想在某一個領域保持優勢,就必須在此領域迅速做大。當你成為某個領域的領頭羊時,即便投資回報率相同,你也能更輕易地獲得比弱小的同行更大的收益。而若沒有實力迅速在某個領域做大,就要不停地尋找新的發展領域,才能保證獲得較好的回報。
5、零和遊戲原理
  零和遊戲是指一項遊戲中,遊戲者有輸有贏,一方所贏正是另一方所輸,遊戲的總成績永遠為零,零和遊戲原理之所以廣受關注,主要是因為人們在社會的方方面面都能發現與零和遊戲類似的局面,勝利者的光榮後面往往隱藏著失敗者的辛酸和苦澀。 20世紀,人類經歷兩次世界大戰、經濟高速增長,科技進步、全球一體化以及日益嚴重的環境污染,零和遊戲觀念正逐漸被雙贏觀念所取代。人們開始認識到利已不一定要建立在損人的基礎上。通過有效合作皆大歡喜的結局是可能出現的。但從零和遊戲走向雙贏,要求各方面要有真誠合作的精神和勇氣,在合作中不要小聰明,不要總想占別人的小便宜,要遵守遊戲規則,否則雙贏的局面就不可能出現,最終吃虧的還是合作者自己。
6、華盛頓合作規律
  華盛頓合作規律說的是一個人敷衍了事,兩個人互相推諉,三個人則永無成事之日。多少有點類似於我們三個和尚的故事。人與人的合作,不是人力的簡單相加,而是要複雜和微妙得多。在這種合作中,假定每個人的能力都為1,那麼,10個人的合作結果有時比10大得多,有時,甚至比1還要小。因為人不是靜止物,而更像方向各異的能量,相互推動時,自然事半功倍,相互抵觸時,則一事無成。我們傳統的管理理論中,對合作研究得並不多,最直觀的反映就是,目前的大多數管理制度和行為都是致力於減少人力的無謂消耗,而非利用組織提高人的效能。換言之,不妨說管理的主要目的不是讓每個人做得更好,而是避免內耗過多。
7、手錶定理
  手錶定理是指一個人有一隻表時,可以知道現在是幾點鐘,當他同時擁有兩隻表時,卻無法確定。兩隻手錶並不能告訴一個人更準確的時間,反而會讓看表的人失去對準確時間的信心。手錶定理在企業經營管理方面,給我們一種非常直觀的啟發,就是對同一個人或同一個組織的管理,不能同時採用兩種不同的方法,不能同時設置兩個不同的目標,甚至每一個人不能由兩個人同時指揮,否則將使這個企業或這個人無所適從。手錶定理所指的另一層含義在於,每個人都不能同時選擇兩種不同的價值觀,否則,你的行為將陷於混亂。
8、不值得定律
  不值得定律最直觀的表述是:不值得做的的事情,就不值得做好。這個定律再簡單不過了,重要性卻時時被人們忽視遺忘。不值得定律反映人們的一種心理,一個人如果從事的是一份自認為不值得做的事情,往往會保持冷嘲熱諷,敷衍了事的態度,不僅成功率低,而且即使成功,也不覺得有多大的成就感。因此,對個人來說,應在多種可供選擇的奮鬥目標及價值觀中挑選一種,然後為之奮鬥。選擇你所愛的,愛你所選擇的,才可能激發我們的鬥志,也可以心安理得。而對一個企業或組織來說,則要很好地分析員工的性格特性,合理分配工作,如讓成就欲較強的職工單獨或牽頭完成具有一定風險和難度的工作,並在其完成時,給予及時的肯定和讚揚;讓依附欲較強的職工,多加參與某個團隊協同工作;讓權力欲較強的職工,擔任一個與之能力相適應的主管。同時要加強員工對企業目標的認同感,讓員工感覺到自己所做的工作是值得的,這樣才能激發職工的熱情。
9、蘑菇管理
  蘑菇管理是許多組織對待初出茅廬者的一種管理方法,初學者被置於陰暗的角落(不受重視的部門,或打雜跑腿的工作),澆上一頭大糞(無端的批評、指責、代人受過),任其自生自滅(得不到必要的指導和提攜)。相信很多人都有過這樣一段蘑菇的經歷,這不一定是什麼壞事,尤其是當一切剛剛開始的時候,當幾天蘑菇,能夠消除我們很多不切實際的幻想,讓我們更加接近現實,看問題也更加實際。一個組織,一般對新進的人員都是一視同仁,從起薪到工作都不會有大的差別。無論你是多麼優秀的人才,在剛開始的時候,都只能從最簡單的事情做起,蘑菇的經歷,對於成長中的年輕人來說,就象蠶繭,是羽化前必須經歷的一步。所以,如何高效率地走過生命的這一段,從中盡可能汲取經驗,成熟起來,並樹立良好的值得信賴的個人形象,是每個剛入社會的年輕人必須面對的課題。
10、奧卡姆剃刀定律
  12世紀,英國奧卡姆的威廉主張唯名論,只承認確實存在的東西,認為那些空洞無物的普遍性概念都是無用的累贅,應當被無情地剃除。他主張如無必要,勿增實體。這就是常說的奧卡姆剃刀。這把剃刀曾使很多人感到威脅,被認為是異端邪說,威廉本人也因此受到迫害。然而,並未損害這把刀的鋒利,相反,經過數百年的歲月,奧卡姆剃刀已被歷史磨得越來越快,並早已超載原來狹窄的領域,而具有廣泛、豐富、深刻的意義。  奧卡姆剃刀定律在企業管理中可進一步演化為簡單與複雜定律:把事情變複雜很簡單,把事情變簡單很複雜。這個定律要求,我們在處理事情時,要把握事情的主要實質,把握主流,解決最根本的問題,尤其要順應自然,不要把事情人為地複雜化,這樣才能把事情處理好。
--> 閱讀更多...

(轉)Linux 2.6內核Makefile分析

(轉)Linux 2.6內核Makefile分析
由於Linux的獨特優勢,使越來越多的企業和科研機構把目光轉向Linux的開發和研究上。目前Linux最新的穩定內核版本為2.6.17,但是當今絕大部分對於Linux Makefile的介紹文章都是基於2.4內核的,可以說關於2.6內核Makefile相關的文章鳳毛麟角,筆者抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對內核的理解,同時也希望能對Linux在公司的推廣起到一定的推動作用,算是抛磚引玉吧!
1 Makefile組織層次
Linux的Make體系由如下幾部分組成:
Ø 頂層Makefile
頂層Makefile通過讀取配置檔,遞迴編譯內核代碼樹的相關目錄,從而產生兩個重要的目標檔:vmlinux和模組。
Ø 內核相關Makefile
位於arch/$(ARCH) 目錄下,為頂層Makefile提供與具體硬體體協結構相關的資訊。
Ø 公共編譯規則定義檔。
包括Makefile.build 、Makefile.clean、Makefile.lib、Makefile.host等檔組成。這些檔位於scripts目錄中,定義了編譯需要的公共的規則和定義。
Ø 內核配置檔 .config
通過調用make menuconfig或者make xconfig命令,用戶可以選擇需要的配置來生成期望的目標檔。
Ø 其他Makefile
主要為整個Makefile體系提供各自模組的目標檔定義,上層Makefile根據它所定義的目標來完成各自模組的編譯。
2 Makefile的使用
在編譯內核之前,用戶必須首先完成必要的配置。Linux內核提供了數不勝數的功能,支援眾多的硬體體系結構,這就需要用戶對將要生成的內核進行裁減。內核提供了多種不同的工具來簡化內核的配置,最簡單的一種是字元介面下命令行工具:
make config
這個工具會依次遍曆內核所有的配置項,要求用戶進行逐項的選擇配置。這個工具會耗費用戶太多時間,除非萬不得以(你的編譯主機不支援其他配置工具)一般不建議使用。
用戶還可以使用利用ncurse庫編制的圖形介面工具,這就是大名鼎鼎的:
make menuconfig
相信以前對2.4內核比較熟悉的用戶一定不會陌生。當然在2.6內核中提供了更漂亮和方便的基於X11的圖形配置工具:
make xconfig
當用戶使用這個工具對Linux內核進行配置時,介面下方會出現這個配置項相關的幫助資訊和簡單描述,當你對內核配置選項不太熟悉時,建議你使用這個工具來進行內核配置。
當用戶完成配置後,配置工具會自動生成.config檔,它被保存在內核代碼樹的根目錄下。用戶可以很容易找到它,當然用戶也可以直接對這個檔進行簡單的修改。但是當你修改過配置檔之後,你必須通過下面的命令來驗證和更新配置:
make oldconfig
跟2.4版本的不同之處在於,用戶不需要顯示的調用make dep命令來生成依賴檔,內核會自動維護代碼間的依賴關係。
當一切工作完成以後,用戶只需要簡單鍵入make,剩下所有的工作makefile就會自動替你完成了。
3 Makefile編譯流程
當用戶使用Linux的Makefile編譯內核版本時,Makefile的編譯流程如下:
Ø 使用命令行或者圖形介面配置工具,對內核進行裁減,生成.config配置檔
Ø 保存內核版本資訊到 include/linux/version.h
Ø 產生符號鏈結 include/asm,指向實際目錄 include/asm-$(ARCH)
Ø 為最終目標檔的生成進行必要的準備工作
Ø 遞迴進入 /init 、/core、 /drivers、 /net、 /lib等目錄和其中的子目錄來編譯生成所有的目標檔
Ø 鏈結上述過程產生的目標檔生成vmlinux,vmlinux存放在內核代碼樹的根目錄下
Ø 最後根據 arch/$(ARCH)/Makefile檔定義的後期編譯的處理規則建立最終的映象bootimage,包括創建引導記錄、準備initrd映象和相關處理
4 Makefile關鍵規則和定義描述
1) 目標定義
目標定義是Makefile檔的核心部分,目標定義通知Makefile需要生成哪些目標檔、如何根據特殊的編譯選項鏈結目標檔,同時控制哪些子目錄要遞迴進入進行編譯。
這個例子Makefile檔位於/fs/ext2目錄 :
#
# Makefile for the linux ext2-filesystem routines.
#
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o \
ioctl.o namei.o super.o symlink.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
ext2-$(CONFIG_EXT2_FS_POSIX_ACL) += acl.o
ext2-$(CONFIG_EXT2_FS_SECURITY) += xattr_security.o
ext2-$(CONFIG_EXT2_FS_XIP) += xip.o
這表示與ext2相關的目標檔由 ext2-y定義的檔列表組成,其中ext2-$(*)是由內核配置檔.config中的配置項決定,最終Makefile會在這個目錄下統一生成一個目標檔ext2.o(由obj-$(CONFIG_EXT2_FS)決定)。其中obj-y表示為生成vmlinux檔所需要的目標檔集合,具體的檔依賴於內核配置。
Makefile會編譯所有的$(obj-y)中定義的檔,然後調用鏈結器將這些檔鏈結到built-in.o文件中。最終built-in.o檔通過頂層Makefile鏈結到vmlinux中。值得注意的是$(obj-y)的檔順序很重要。列表檔可以重複,檔第一次出現時將會鏈結到built-in.o中,後來出現的同名檔將會被忽略。檔順序直接決定了他們被調用的順序,這一點讀者需要特別注意。
讀者可能會在某些Makefile中發現lib-y定義,所有包含在lib-y定義中的目標檔都將會被編譯到該目錄下一個統一的庫檔中。值得注意的是lib-y定義一般被限制在 lib 和arch/$(ARCH)/lib 目錄中。
體系makefile檔和頂層makefile檔共同定義了如何建立vmlinux檔的規則。
$(head-y) 列舉首先鏈結到vmlinux的物件檔。
$(libs-y) 列舉了能夠找到lib.a檔的目錄。
其餘的變數列舉了能夠找到内嵌物件檔的目錄。
$(init-y) 列舉的物件位於$(head-y)物件之後。
然後是如下位置順序:
$(core-y), $(libs-y), $(drivers-y) 和 $(net-y)。
頂層makefile定義了所有通用目錄,arch/$(ARCH)/Makefile檔只需增加體系相關的目錄。
例如: #arch/i386/Makefile
libs-y += arch/i386/lib/
core-y += arch/i386/kernel/ \
arch/i386/mm/ \
arch/i386/$(mcore-y)/ \
arch/i386/crypto/
drivers-$(CONFIG_MATH_EMULATION) += arch/i386/math-emu/
drivers-$(CONFIG_PCI) += arch/i386/pci/
…………………………………………
2) 目錄遞迴
Makefile檔只負責當前目錄下的目標檔,子目錄中的檔由子目錄中的makefile負責編譯,編譯系統使用obj-y 和 obj-m來自動遞迴編譯各個子目錄中的檔。
對於fs/Makefile:
obj-$(CONFIG_EXT2_FS) += ext2/
如果在內核配置檔.config中,CONFIG_EXT2_FS被設置為y或者m,則內核makefile會自動進入ext2目錄來進行編譯。內核Makefile只使用這些資訊來決定是否需要編譯這個目錄,子目錄中的makefile規定哪些檔編譯為模組,哪些檔編譯進內核。
3) 依賴關係
Linux Makefile通過在編譯過程中生成的 .檔案名.o.cmd(比如對於main.c檔,它對應的依賴檔案名為.main.o.cmd)來定義相關的依賴關係。
一般檔的依賴關係由如下部分組成:
Ø 所有的前期依賴檔(包括所有相關的*.c 和 *.h)
Ø 所有與CONFIG_選項相關的檔
Ø 編譯目標檔所使用到的命令行
位於init目錄下的main.c檔的依賴檔.main.o.cmd內容如下,讀者可以結合起來理解上述檔依賴關係的三個組成部分:
cmd_init/main.o := gcc -m32 -Wp,-MD,init/.main.o.d -nostdinc -isystem /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include -D__KERNEL__ -Iinclude -Iinclude2 -I/home/linux/linux-2.6.17.11/include -include include/linux/autoconf.h -I/home/linux/linux-2.6.17.11/init -Iinit -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Os -fomit-frame-pointer -pipe -msoft-float -mpreferred-stack-boundary=2 -march=i686 -mcpu=pentium4 -mregparm=3 -ffreestanding -I/home/linux/linux-2.6.17.11/include/asm-i386/mach-default -Iinclude/asm-i386/mach-default -D"KBUILD_STR(s)=\#s" -D"KBUILD_BASENAME=KBUILD_STR(main)" -D"KBUILD_MODNAME=KBUILD_STR(main)" -c -o init/.tmp_main.o /home/linux/linux-2.6.17.11/init/main.c
deps_init/main.o := \
/home/linux/linux-2.6.17.11/init/main.c \
$(wildcard include/config/x86/local/apic.h) \
$(wildcard include/config/acpi.h) \
# 由於篇幅的關係,此處略去一些定義
……………………………………..
include2/asm/mpspec_def.h \
/home/linux/linux-2.6.17.11/include/asm-i386/mach-default/mach_mpspec.h \
include2/asm/io_apic.h \
include2/asm/apic.h \
init/main.o: $(deps_init/main.o)
$(deps_init/main.o):
4) 特殊規則
特殊規則使用在內核編譯需要規則定義而沒有相應定義的時候。典型的例子如編譯時頭檔的產生規則。其他例子有體系makefile編譯引導映射的特殊規則。特殊規則寫法同普通的makefile規則。
編譯程序在makefile所在的目錄不能被執行,因此所有的特殊規則需要提供前期檔和目標檔的相對路徑。
定義特殊規則時將使用到兩個變數:
$(src): $(src)是對於makefile檔目錄的相對路徑,當使用代碼樹中的檔時
使用該變數$(src)。
$(obj): $(obj)是目標檔目錄的相對路徑。生成檔使用$(obj)變數。
例如: #drivers/scsi/Makefile
$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
$(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl
這就是使用普通語法的特殊編譯規則。
目標檔依賴於兩個前提檔。目標檔的首碼是$(obj), 前提檔的首碼是
$(src)(因為它們不是生成檔)。
5) 引導映象
體系makefile檔定義了編譯vmlinux檔的目標物件,將它們壓縮和封裝成引導代碼,並複製到合適的位置。這包括各種安裝命令。在Linux中Makefile無法為所有的體系結構提供標準化的方法,因此常需要具體硬體體系結構下makefile提供附加處理規則。
附加處理過程常位於arch/$(ARCH)/下的boot/目錄。
內核編譯體系無法在boot/目錄下提供一種便捷的方法創建目標系統檔。因此arch/$(ARCH)/Makefile要調用make命令在boot/目錄下建立目標系統檔。建議使用的方法是在arch/$(ARCH)/Makefile中設置調用,並且使用完整路徑引用arch/$(ARCH)/boot/Makefile。
例如: #arch/i386/Makefile
boot := arch/i386/boot
bzImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
建議使用"$(Q)$(MAKE) $(build)=
"方式在子目錄中調用make命令。
當執行不帶參數的make命令時,將首先編譯第一個目標物件。在頂層makefile中第一個目標物件是all:。
一個體系結構需要定義一個默認的可引導映射。
增加新的前提檔給all目標可以設置不同於vmlinux的默認目標物件。
例如: #arch/i386/Makefile
all: bzImage
當執行不帶參數的"make"命令時,bzImage文件將被編譯。
6) 常用編譯命令
if_changed
如果必要,執行傳遞的命令。
用法:
$(builtin-target): $(obj-y) FORCE
$(call if_changed,link_o_target)
當這條規則被使用時它將檢查哪些檔需要更新,或命令行被改變。後面這種情況將迫使
重新編譯編譯選項被改變的執行檔。使用if_changed的目標物件必須列舉在$( builtin-target)中,否則命令行檢查將失敗,目標一直會編譯。
if_changed_dep
如果必要,執行傳遞的命令並更新依賴檔。
用法:
%.o: %.S FORCE
$(call if_changed_dep,as_o_S)
當這條規則被使用時它將檢查哪些檔需要更新,或命令行被改變。同時它會重新檢測依賴關係的改變並將生成新的依賴檔。這是與if_changed命令的區別。
7) 定制命令
當正常執行帶編譯命令時命令的簡短資訊會被顯示(要想顯示詳細的命令,請在命令行中加入V=1)。要讓定制命令具有這種功能需要設置兩個變數:
quiet_cmd_ - 將被顯示的內容
cmd_ - 被執行的命令
例如: #
quiet_cmd_image = BUILD $@
cmd_image = $(obj)/tools/build $(BUILDFLAGS) \
$(obj)/vmlinux.bin > $@
targets += bzImage
$(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE
$(call if_changed,image)
@echo 'Kernel: $@ is ready'
執行make命令編譯$(obj)/bzImage目標時將顯示:
BUILD arch/i386/boot/bzImage
8) 預處理鏈結腳本
當編譯vmlinux映射時將使用arch/$(ARCH)/kernel/vmlinux.lds鏈結腳本。
相同目錄下的vmlinux.lds.S檔是這個腳本的預處理的變體。內核編譯系統知曉.lds
文件。並使用規則*lds.S -> *lds。
例如: #arch/i386/kernel/Makefile
always := vmlinux.lds
#Makefile
export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
$(always)賦值語句告訴編譯系統編譯目標是vmlinux.lds。$(CPPFLAGS_vmlinux.lds)
賦值語句告訴編譯系統編譯vmlinux.lds目標的編譯選項。
編譯*.lds時將使用到下面這些變數:
CPPFLAGS : 定義在頂層Makefile
EXTRA_CPPFLAGS : 可以設置在編譯的makefile檔中
CPPFLAGS_$(@F) : 目標編譯選項。注意要使用檔全名。
9) 主機輔助程式的編譯
內核編譯系統支援在編譯階段編譯主機可執行程式。為了使用主機程式需要兩個步驟:第一個步驟使用hostprogs-y變數告訴內核編譯系統有主機程式可用。第二步給主機程式添加潛在的依賴關係。有兩種方法,在規則中增加依賴關係或使用$(always)變數。這一部分的內容相對於其他內核檔的編譯要簡單的多,感興趣的讀者可以參考scripts/Makefile.build中的相關內容。
10) Clean機制
clean命令清除在編譯內核生成的大部分檔,例如主機程式,列舉在 $(hostprogs-y)、$(hostprogs-m)、$(always)、$(extra-y)和$(targets)中目標檔都將被刪除。代碼目錄數中的"*.[oas]"、"*.ko"檔和一些由編譯系統產生的附加檔也將被刪除。
附加檔可以使用$(clean-files)進行定義。
例如: #drivers/pci/Makefile
clean-files := devlist.h classlist.h
當執行"make clean"命令時, "devlist.h classlist.h"兩個檔將被刪除。內核編譯系統默認這些檔與makefile具有相同的相對路徑,否則需要設置以'/'開頭的絕對路徑。
刪除整個目錄使用以下方式:
例如: #scripts/package/Makefile
clean-dirs := $(objtree)/debian/
這樣就將刪除包括子目錄在內的整個debian目錄。如果不使用以'/'開頭的絕對路徑內核編譯系統見默認使用相對路徑。
通常內核編譯系統根據"obj-* := dir/"進入子目錄,但是在體系makefile中需要顯式使用如下方式:
例如: #arch/i386/boot/Makefile
subdir- := compressed/
上面賦值語句指示編譯系統執行"make clean"命令時進入compressed/目錄。
在編譯最終的引導映射檔的makefile中有一個可選的目標物件名稱是archclean。
例如: #arch/i386/Makefile
archclean:
$(Q)$(MAKE) $(clean)=arch/i386/boot
當執行"make clean"時編譯器進入arch/i386/boot並象通常一樣工作。arch/i386/boot 中的makefile檔可以使用subdir-標識進入更下層的目錄。
注意1: arch/$(ARCH)/Makefile不能使用"subdir-",因為它被包含在頂層makefile檔中,在這個位置編譯機制是不起作用的。
注意2: 所有列舉在core-y、libs-y、drivers-y和net-y中的目錄將被"make clean"命令清除。
4 小結
隨著Linux的飛速發展,越來越多的開發人員將關注的焦點集中到Linux的研究和開發上。如果想對Linux內核進行研究和開發,就必須首先熟悉Linux 內核Makefile的組織和編譯過程。目前Linux最新的穩定內核版本為2.6.17,但是當今絕大部分對於Linux Makefile的介紹都是基於2.4內核的,可以說關於2.6內核Makefile相關的文章鳳毛麟角,我特意抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對內核的理解,同時也希望能對Linux在公司的推廣起到一定的推動作用。
本文來自CSDN博客,http://blog.csdn.net/mbtrend/archive/2008/10/05/3016889.aspx
--> 閱讀更多...