日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

main 函数解析(二)—— Linux-0.11 学习笔记(六)

發布時間:2025/3/15 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 main 函数解析(二)—— Linux-0.11 学习笔记(六) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

main函數解析(二)——Linux-0.11 學習筆記(六)

4.6 blk_dev_init函數

void blk_dev_init(void) {int i;for (i=0 ; i<NR_REQUEST ; i++) {request[i].dev = -1; //表示空閑request[i].next = NULL;} }

這里的request是一個全局的數組。

/** The request-struct contains all necessary data* to load a nr of sectors into memory*/ struct request request[NR_REQUEST]; //請求項數組 #define NR_REQUEST 32 ...struct request {int dev; //設備號,若為-1,則表示空閑int cmd; // 命令 READ or WRITE int errors; //操作時產生的錯誤次數unsigned long sector; // 起始扇區unsigned long nr_sectors; // 讀/寫扇區數char * buffer; //數據緩沖區struct task_struct * waiting; // 任務等待操作執行完成的地方struct buffer_head * bh; // 緩沖區頭指針struct request * next; //指向下一個struct request,構成單向鏈表 };

以上的代碼要想完全理解,就牽扯到塊設備驅動程序了。

每個塊設備的當前請求指針與請求項數組中該設備的請求項鏈表共同構成了該設備的請求隊列。項與項之間利用字段next指針形成鏈表。因此塊設備項和相關的請求隊列形成如圖所示結構。請求項采用數組加鏈表結構的主要原因是為了滿足兩個目的:一是利用請求項的數組結構在搜索空閑請求塊時可以進行循環操作,搜索訪問時間復雜度為常數,因此程序可以編制得很簡潔;二是為滿足電梯算法 (Elevator Algorithm) 插入請求項操作,因此也需要采用鏈表結構。圖中畫出了硬盤設備當前具有4個請求項,軟盤設備具有1個請求項,而虛擬盤設備目前暫時沒有讀寫請求項。

對于一個當前空閑的塊設備,當 ll_rw_block()函數為其建立第一個請求項時,會讓該設備的當前請求項指針current_request直接指向剛建立的請求項,并且立刻調用對應設備的請求項操作函數開始執行塊設備讀寫操作。當一個塊設備已經有幾個請求項組成的鏈表存在,ll_rw_block()就會利用電梯算法,根據磁頭移動距離最小原則,把新建的請求項插入到鏈表適當的位置處。

以上內容摘自趙炯博士的《Linux內核完全剖析》。

雖然看完不甚理解,但至少明白blk_dev_init函數了。其實很簡單,就是把數組的每一項的設備號設為 -1,表示空閑,然后把 next 設為 NULL(因為空閑,所以沒有被插入鏈表)。

4.7 chr_dev_init函數

此函數實現為空。

void chr_dev_init(void) { }

4.8 tty_init函數

void tty_init(void) {rs_init(); // 初始化串行中斷程序和串行接口1、2 con_init(); // 初始化控制臺終端 }

4.8.1 rs_init函數

void rs_init(void) {set_intr_gate(0x24,rs1_interrupt); // 設置串行口1的中斷門向量set_intr_gate(0x23,rs2_interrupt); // 設置串行口2的中斷門向量init(tty_table[1].read_q.data); // 初始化串行口1,參數是端口基地址init(tty_table[2].read_q.data); // 初始化串行口2,參數是端口基地址outb(inb_p(0x21)&0xE7,0x21); // 允許8259A主片響應IRQ3、IRQ4中斷請求, 0x21 對應 8259A 主片的中斷屏蔽寄存器 }

4.8.1.1 設置中斷門

#define set_intr_gate(n,addr) \_set_gate(&idt[n],14,0,addr)

set_intr_gate(n,addr)的宏展開是_set_gate(&idt[n],14,0,addr)。

上圖是中斷門描述符的格式,根據 [11:8] = 14,可以知道代碼中的14表示中斷門。

宏_set_gate(gate_addr,type,dpl,addr)用于設置門描述符。具體的分析參見 main函數解析(一)——Linux-0.11 學習筆記(五)

這個宏根據參數中的

  • 門描述符類型 type
  • 描述符特權級 dpl
  • 中斷或異常處理過程的偏移地址地址 addr

設置位于地址 gate_addr 處的門描述符。

所以, set_intr_gate(n,addr)表示在元素idt[n](idt[]是中斷描述符表,其實是數組,一共有 256 個表項,一個表項占8字節。)位置安裝一個中斷處理過程的偏移地址地址為 addr 的、特權級為0的中斷門描述符。

Linux-0.11 系統

  • 把主片的中斷號設置為 0x20~0x27;
  • 把從片的中斷號設置為 0x28~0x2f。

根據上圖可以知道,串行口1的中斷號是0x24,串行口2的中斷號是0x23;

順便提一下:使用中斷門(interrupt gate) 來構造中斷調用機制的,當 processor 進入 interrupt handler 執行前, 會將 eflags 的值壓入棧中保存并且會清除 eflags.IF 標志位,這意味著進入中斷后不再響應其他的可屏蔽中斷。

4.8.1.2 串口的初始化

static void init(int port) // port 是端口基地址 {... }

串口初始化,說白了就是配置一些寄存器,使串口可以收發數據。

說到這個串口,內容還挺多,不了解的一定要參考我的博文 PC 機 UART(NS8250)詳解

init(tty_table[1].read_q.data);傳入的參數是tty_table[1].read_q.data,如果沒有猜錯的話,tty_table[1].read_q.data一定是端口的基地址。為了驗證猜測,我們找找和tty_table有關的定義。

果然,在kernel\chr_drv\tty_io.c中看到了以下代碼:

struct tty_struct tty_table[] = {{...},{...{0x3f8,0,0,0,""}, /* rs 1 */{0x3f8,0,0,0,""},{0,0,0,0,""}},{...{0x2f8,0,0,0,""}, /* rs 2 */{0x2f8,0,0,0,""},{0,0,0,0,""}} };

0x3f8和0x2f8正是串口1和2的端口基地址。

static void init(int port) // port 是端口基地址 {outb_p(0x80,port+3); /* set DLAB of line control reg */outb_p(0x30,port); /* LS of divisor (48 -> 2400 bps */outb_p(0x00,port+1); /* MS of divisor */outb_p(0x03,port+3); /* reset DLAB */outb_p(0x0b,port+4); /* set DTR,RTS, OUT_2 */outb_p(0x0d,port+1); /* enable all intrs but writes */(void)inb(port); /* read data port to reset things (?) */ }

目前遇到的讀寫端口的宏有4個:

outb_p(value,port)—— 把value寫入端口port.

inb_p(port)——讀取端口port的值。

outb(value,port)—— 把value寫入端口port.

inb(port)——讀取端口port的值。

前2個帶延時,后2個不帶延時。

這4個宏,具體的定義和分析可以參考我的博文 main函數解析(一)——Linux-0.11 學習筆記(五)

第3行:outb_p(0x80,port+3); 把0x80寫入端口(0x3f8+3=0x3fb,即 LCR 寄存器),也就是使 DLAB=1。

第4行:outb_p(0x30,port);寫波特率因子的低字節為0x30

第5行:outb_p(0x00,port+1);寫波特率因子的高字節為0x00

所以,波特率因子為 0x0030=48。

波特率和因子之間的關系是:

公式變形一下:

所以

=1.8432MHz48?16=1843200768=2400波特率=1.8432MHz48?16=1843200768=2400

第6行:outb_p(0x03,port+3);無奇偶校驗位、8 位數據位和 1 位停止位,同時復位 DLAB;

第7行:outb_p(0x0b,port+4);若要讓 UART 的中斷請求信號能夠送到 8259A 中斷控制器,就需要把MODEM 控制寄存器 MCR 的位3(OUT2) 置位。因為在PC 機中,該位控制著 INTRPT 引腳到 8259A 的電路。MCR的位 1 和位 0 分別用于控制 MODEM , 當這兩位置位時,UART的數據終端就緒引腳(DTR)和請求發送引腳(RTS)輸出有效。

第8行:outb_p(0x0d,port+1);寫IER(Interrupt enable register,中斷允許寄存器 )

0x0d = 1101b
[3] = 1 允許modem狀態中斷;
[2] = 1 允許接收器線路狀態中斷;
[1] = 0 禁止發送保持寄存器空中斷;
[0] = 1 允許接收到數據中斷;

第9行:(void)inb(port);讀接收緩存寄存器 ,以進行復位??其實這句話我也不清楚是否必要,先在這里留個疑問。

4.8.2 con_init()函數

con_init()函數(在文件linux/kernel/console.c中)首先根據 setup.s 程序取得的系統硬件參數初始化幾個本函數專用的靜態全局變量。然后根據顯示卡模式(單色還是彩色)和顯示卡類型(EGA/VGA、CGA、MDA 等) 分別設置顯示內存起始位置以及顯示索引寄存器和顯示數值寄存器的端口號。最后設置鍵盤中斷陷阱描述符并打開對鍵盤中斷的屏蔽位。

顯卡的歷史

磨刀不誤砍柴工,在理解這個函數之前,不妨先了解一下顯卡的歷史。

顯卡的前身

MDA

最早的顯卡稱為顯示適配器,在“黑底白字”的DOS年代,對顯示的要求是極低的。最早的顯示類型是 MDA(Monochrome Display Adapter),只能區別出黑白兩色。早期的8080、8088,一直到80286都是使用這種類型的顯示適配器。它的功能極為簡單,一般集成 16KB 顯存,是不為人關注的電腦配件。

CGA
到了286時,PC上出現了一些和圖形相關的軟件,因此出現了一種四色適配器,只能識別三原色和黑白。由于這是第一種彩色的顯示適配器,所以稱為 CGA(Color Graphics Adaptor,彩色圖形適配器)。CGA 時代對顯卡的要求已經大幅度提高,但是當時的制作工藝仍然遠遠高于顯卡芯片的需求,因此 CGA 顯示適配器依舊被整合在主板上,以一塊單芯片的方式來實現,所以“顯卡”尚未誕生。

EGA
CGA 的分辨率太低,于是又有了 EGA(Enhanced Graphics Adapter,增強圖形適配器)。在顯示性能方面(顏色和分辨率),EGA 介于 CGA 和 VGA 之間,可以在高達640x350的分辯率下達到16色。

顯卡的誕生與換代

以上MDA、CGA、EGA 三種標準都是以 TTL 數字信號輸出,而之后的 VGA 標準采用模擬信號輸出,因而其彩色顯示能力大大加強,原則上可以顯示無窮多的顏色。VGA 最初代表分辨率,在個人電腦的啟蒙時代,能夠輸出 VGA(640×480)這樣的分辨率并不是一件易事,VGA 標準的出現對顯示輸出設備首次提出了較高的要求,于是催生了 VGA Card,顯卡正式誕生!

第一代顯卡:VGA Card,支持256色顯示,1988年

第二代顯卡:Graphics Card,支持Windows圖形加速,1991年

第三代顯卡:Video Card,支持視頻加速,1994年

第四代顯卡:3D Accelerator Card,支持3D加速,1994年

第五代顯卡:GPU圖形處理器,支持硬件 T&L(Transform and Lighting,多邊形轉換與光源處理),1999年

現代和未來顯卡:GPGPU(General-purpose computing on graphics processing units,簡稱 GPGPU 或 GP2U)通用計算圖形處理器,支持幾何著色、物理加速、高清解碼、科學計算……

和顯示有關的靜態全局變量的初始化

void con_init(void) {register unsigned char a;// 定義寄存器變量 a,該變量將被保存在一個寄存器中,以便于高效訪問和操作。若想指定存放的寄存器(如 eax) ,則可寫成 register unsigned char a asm("ax");char *display_desc = "????";char *display_ptr;video_num_columns = ORIG_VIDEO_COLS;video_size_row = video_num_columns * 2;video_num_lines = ORIG_VIDEO_LINES; //每屏25行video_page = ORIG_VIDEO_PAGE; // 我覺得有點問題video_erase_char = 0x0720; // 擦除字符(0x20是空格,0x07是屬性)...

在文件linux/kernel/console.c中有宏定義:

#define ORIG_X (*(unsigned char *)0x90000) #define ORIG_Y (*(unsigned char *)0x90001) #define ORIG_VIDEO_PAGE (*(unsigned short *)0x90004) #define ORIG_VIDEO_MODE ((*(unsigned short *)0x90006) & 0xff) #define ORIG_VIDEO_COLS (((*(unsigned short *)0x90006) & 0xff00) >> 8) #define ORIG_VIDEO_LINES (25) #define ORIG_VIDEO_EGA_AX (*(unsigned short *)0x90008) #define ORIG_VIDEO_EGA_BX (*(unsigned short *)0x9000a) #define ORIG_VIDEO_EGA_CX (*(unsigned short *)0x9000c)

右邊的地址,其實對應 setup.s 中取得的系統硬件參數的存放位置。如果忘了,可以參考我的博文 setup.s 分析—— Linux-0.11 學習筆記(二)

第9行:video_num_columns = (((*(unsigned short *)0x90006) & 0xff00) >> 8);

對比匯編代碼,也就是把AH的值(字符列數)賦給全局變量video_num_columns ;

`setup.s中相關代碼如下

! 獲取顯示卡當前的顯示模式! 調用 BIOS 中斷 0x10,功能號 ah = 0x0f! 返回: ah=字符列數; al=顯示模式;bh=當前顯示頁。! ds = 0x9000! 0x90004(l個字)存放當前頁;0x90006(1字節)存放顯示模式;0x90007(1字節)存放字符列數。mov ah,#0x0fint 0x10mov [4],bx ! bh = 當前顯示頁mov [6],ax ! al = 顯示模式, ah = 字符列數(窗口寬度)

第10行:video_size_row = video_num_columns * 2;算出每行字符需使用的字節數。

第12行:video_page = (*(unsigned short *)0x90004);

注意,video_page是static unsigned char類型。

根據匯編代碼第8行,應該是0x90005處的一個字節存放當前活動頁碼,所以我認為第12行應該改為:

video_page = (*(unsigned char *)0x90005);

在所有源碼文件中搜了一波發現video_page這個變量沒有被用到,好吧,暫且不管,接著往下看。

顯示模式

#define ORIG_VIDEO_MODE ((*(unsigned short *)0x90006) & 0xff)

根據上面匯編代碼的第9行,ORIG_VIDEO_MODE中的值是顯示模式。

其取值對應的含義如下表。

ALTypeFormatCellColorsAdapterAddrMonitor
0text40x258x8*16/8 (shades)CGA,EGAb800Composite
1text40x258x8*16/8CGA,EGAb800Comp,RGB,Enh
2text80x258x8*16/8 (shades)CGA,EGAb800Composite
3text80x258x8*16/8CGA,EGAb800Comp,RGB,Enh
4graphic320x2008x84CGA,EGAb800Comp,RGB,Enh
5graphic320x2008x84 (shades)CGA,EGAb800Composite
6graphic640x2008x82CGA,EGAb800Comp,RGB,Enh
7text80x259x14*3 (b/w/bold)MDA,EGAb000TTL Mono
8,9,0aHPCjr modes
0bH,0cH(reserved; internal to EGA BIOS)
0dHgraphic320x2008x816EGA,VGAa000Enh,Anlg
0eHgraphic640x2008x816EGA,VGAa000Enh,Anlg
0fHgraphic640x3508x143 (b/w/bold)EGA,VGAa000Enh,Anlg,Mono
10Hgraphic640x3508x144 or 16EGA,VGAa000Enh,Anlg
11Hgraphic640x4808x162VGAa000Anlg
12Hgraphic640x4808x1616VGAa000Anlg
13Hgraphic640x4808x16256VGAa000Anlg

Notes: With EGA, VGA, and PCjr you can add 80H to AL to initialize a video mode without clearing the screen.

*The character cell size for modes 0-3 and 7 varies, depending on the hardware. On modes 0-3: CGA=8x8, EGA=8x14, and VGA=9x16. For mode 7, MDPA and EGA=9x14, VGA=9x16, LCD=8x8.

if (ORIG_VIDEO_MODE == 7) // 等于7說明是單色{video_mem_start = 0xb0000; // 設置內存起始地址video_port_reg = 0x3b4; // 設置索引寄存器端口video_port_val = 0x3b5; // 設置數據寄存器端口// 注意,這里使用了 BL 在調用中斷 int 0x10 前后是否被改變的方法來判斷卡的類型。if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10){ // BL之前設置的值是0x10,調用中斷后發生改變,則說明是EGA //#define VIDEO_TYPE_EGAM 0x20 /* EGA/VGA in Monochrome Mode*/ video_type = VIDEO_TYPE_EGAM; // 設置顯示類型video_mem_end = 0xb8000; // 設置顯示內存末端地址display_desc = "EGAm"; // 設置顯示描述字符串}else // 沒有改變說明是MDA{video_type = VIDEO_TYPE_MDA;video_mem_end = 0xb2000;display_desc = "*MDA";}}else // 說明是彩色 {video_mem_start = 0xb8000; // 設置內存起始地址video_port_reg = 0x3d4; // 設置索引寄存器端口video_port_val = 0x3d5; // 設置數據寄存器端口if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10){video_type = VIDEO_TYPE_EGAC; // 說明是EGA或者VGA顯卡video_mem_end = 0xbc000; // 設置顯示內存末端地址display_desc = "EGAc"; // EGA彩色}else{video_type = VIDEO_TYPE_CGA; // 設置顯示類型為CGAvideo_mem_end = 0xba000;display_desc = "*CGA";}}

setup.s中相關代碼是:

! 檢查顯示方式(EGA/VGA)并獲取參數。! 調用 BIOS 中斷 0x10,功能號: ah = 0xl2,子功能號: bl = 0xl0! 返回:bh=顯示狀態。 0x00-彩色模式,I/O 端口=0x3dX! 0x01-單色模式,I/O 端口=0x3bX! bl = 安裝的顯示內存。0x00 - 64k! 0x01 - 128k! 0x02 - 192k! 0x03 - 256k! cx = 顯示卡特性參數。!mov ah,#0x12 ! 功能號mov bl,#0x10 ! 子功能號int 0x10mov [8],ax ! 我也不知道這個是什么(╯︵╰)mov [10],bx ! bh=顯示狀態(單色模式/彩色模式),bl=已安裝的顯存大小mov [12],cx ! ch=特性連接器比特位信息,cl=視頻開關設置信息

注意,C代碼使用了 BL 在調用中斷 int 0x10 前后是否被改變的方法來判斷卡的類型。BL在調用中斷之前被賦值為 0x10 ,在調用中斷后,其值可能不變(CGA 或 MDA),也可能變為0~3中的一個(EGA 或 VGA)。

在屏幕右上角顯示顯卡類型

下面的代碼作用是在屏幕右上角顯示描述字符串。采用的方法是直接將字符串寫到顯示內存的相應位置處。首先將顯示指針display_ptr指到屏幕第1行最右端起第4個字符處(每個字符需 2 個字節,因此減 8 ) ,然后循環復制字符串的字符。

display_ptr = ((char *)video_mem_start) + video_size_row - 8;while (*display_desc) // 循環能停止是因為字符串末尾的'\0'{*display_ptr++ = *display_desc++; // 復制字符display_ptr++; // 跳過屬性字節}

在我的實驗環境調試,截圖如下:

和滾屏有關的變量

初始化用于滾屏的變量(主要用于EGA/VGA):

/* Initialize the variables used for scrolling (mostly EGA/VGA) */origin = video_mem_start; // 滾屏起始顯存地址scr_end = video_mem_start + video_num_lines * video_size_row;// 結束地址top = 0; // 最頂端行號bottom = video_num_lines; // 最底端行號 #define ORIG_X (*(unsigned char *)0x90000) // 列號 #define ORIG_Y (*(unsigned char *)0x90001) // 行號 ...// 初始化當前光標所在位置(x,y)和光標對應的顯存位置 pos gotoxy(ORIG_X,ORIG_Y);

第1~2行對應的匯編代碼(setup.s)是

mov ax,#INITSEG !INITSEG = 0x9000mov ds,ax ! ds = 0x9000mov ah,#0x03 ! 功能號=3,獲取光標的位置xor bh,bh ! bh = 頁號 = 0(輸入)int 0x10 ! 輸出: DH=行號,DL=列號mov [0],dx ! 保存光標的行號和列號到 0x90000,共占2字節.

gotoxy函數(kernel\chr_drv\console.c)的實現如下:

/* NOTE! gotoxy thinks x==video_num_columns is ok */ static inline void gotoxy(unsigned int new_x,unsigned int new_y) {if (new_x > video_num_columns || new_y >= video_num_lines)return;x=new_x; // 記錄下當前光標所在的列y=new_y; // 記錄下當前光標所在的行pos=origin + y*video_size_row + (x<<1);// 記錄當前光標所在位置對應的顯存地址。我調試時,video_size_row = 160 }

第4行:判斷參數是否合法。在我的實驗環境中,video_num_lines = 25,即new_y的取值是[0,24];video_num_columns = 80,即new_x的取值是[0,80],為什么可以等于80呢?目前還不知道,我想作者這樣寫肯定有他的道理,后面多留個心。

和顯示有關的代碼就暫時結束了。后面是關于鍵盤的。

允許鍵盤工作

set_trap_gate(0x21,&keyboard_interrupt); //安裝陷阱門,鍵盤的中斷號是0x21, 對應8259A主片的 IRQ1 // 已經反復強調, Linux-0.11 系統把主片的中斷號設置為 `0x20~0x27`; // 把從片的中斷號設置為 `0x28~0x2f`。outb_p(inb_p(0x21)&0xfd,0x21); // 允許鍵盤中斷a=inb_p(0x61); // 讀取鍵盤端口 0x61(8255A端口PB)到 a(之前定義的寄存器變量)outb_p(a|0x80,0x61); // b7置位,禁止鍵盤工作outb(a,0x61); // 再允許鍵盤工作,用以復位鍵盤

第4行:0x21是 8259A 主片命令字OCW1的端口地址,(注意:不是第1行的那個0x21)用于對其中斷屏蔽寄存器 IMR 進行讀/寫操作。

寫到這里,盡管篇幅較長,可是才分析了 2 個函數。

blk_dev_init();chr_dev_init(); //實現為空tty_init();

數了數,距main函數結束,還有10多個函數呢……好了,今天就到這里,明日繼續精進。


參考資料

[0]《Linux內核完全剖析》(趙炯,機械工業出版社,2006)
[1] https://blog.csdn.net/mao0514/article/details/24730097

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的main 函数解析(二)—— Linux-0.11 学习笔记(六)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。