linux0.11內核源碼修仙傳第十六章——獲取硬盤信息及根目錄掛載

🚀 前言

? ? 書接第十四章:linux0.11內核源碼修仙傳第十四章——進程調度之fork函數,在這一節博客中已經通過fork進程創建了一個新的進程1,并且可以被調度,接下來接著主線繼續走下去。希望各位給個三連,拜托啦,這對我真的很重要!!!

目錄

  • 🚀 前言
  • 🏆硬盤基本信息的賦值
  • 🏆硬盤分區表的設置
    • 📃硬盤分區表(Disk Partition Table,DPT)
    • 📃代碼實現
  • 🏆加載根文件系統
    • 📃mount_root 整體解讀
    • 📃內存中用于文件系統的數據結構
      • 文件信息初始化
      • 超級塊初始化
      • inode信息讀取
      • 記錄位圖信息
      • 記錄inode位圖信息
  • 🎯總結
  • 📖參考資料

🏆硬盤基本信息的賦值

? ? 好久沒回顧 main 函數了,來回顧一下:

void main(void)
{···mem_init(main_memory_start,memory_end);trap_init();blk_dev_init();chr_dev_init();tty_init();time_init();sched_init();buffer_init(buffer_memory_end);hd_init();floppy_init();sti();move_to_user_mode();if (!fork()) {		/* we count on this going ok */init();}for(;;) pause();
}

? ? 這里面前面的一堆初始化已經看完了,fork函數也運行了,成功創建了進程1。進程1會返回0,。現在壓力來到了init函數:

void init(void)
{int pid,i;setup((void *) &drive_info);(void) open("/dev/tty0",O_RDWR,0);(void) dup(0);(void) dup(0);printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,NR_BUFFERS*BLOCK_SIZE);printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);if (!(pid=fork())) {close(0);if (open("/etc/rc",O_RDONLY,0))_exit(1);execve("/bin/sh",argv_rc,envp_rc);_exit(2);}if (pid>0)while (pid != wait(&i))/* nothing */;while (1) {if ((pid=fork())<0) {printf("Fork failed in init\r\n");continue;}if (!pid) {close(0);close(1);close(2);setsid();(void) open("/dev/tty0",O_RDWR,0);(void) dup(0);(void) dup(0);_exit(execve("/bin/sh",argv,envp));}while (1)if (pid == wait(&i))break;printf("\n\rchild %d died with code %04x\n\r",pid,i);sync();}_exit(0);	/* NOTE! _exit, not exit() */
}

? ? ok,fine,里面內容很多,沒關系,一點點來,這一篇博文來看第一行 setup 函數的內容。

struct drive_info { char dummy[32]; } drive_info;void init(void)
{setup((void *) &drive_info);···
}

? ? 先來看看傳入的參數:drive_info 。這個變量是來自內存 0x90080 的數據,設置是在main函數的最開始。詳情可以看博客:linux0.11內核源碼修仙傳第二章——setup.s,內存里的存放位置如下所示:

在這里插入圖片描述

? ? 接下來來看setup函數:

static inline _syscall1(int,setup,void *,BIOS)

? ? 好的,這又是一個系統調用,返回類型是int,有一個參數是 void * 類型的BIOS。有關于系統調用可以參考這篇博客:linux0.11內核源碼修仙傳第十四章——進程調度之fork函數。其實直白一點,可以直接去系統調用的sys_call_table里面找對應的函數,這里講結論,它會直接調用 sys_setup 函數:

int sys_setup(void * BIOS)
{···hd_info[0].cyl = *(unsigned short *) BIOS;			// 總柱面數hd_info[0].head = *(unsigned char *) (2+BIOS);		// 磁頭數hd_info[0].wpcom = *(unsigned short *) (5+BIOS);	// 寫入補償hd_info[0].ctl = *(unsigned char *) (8+BIOS);		// 控制字節hd_info[0].lzone = *(unsigned short *) (12+BIOS);	// 邏輯區域起始柱面hd_info[0].sect = *(unsigned char *) (14+BIOS);		// 每磁道扇區數BIOS += 16;	···
}

? ? 上面是這個函數的第一部分,對硬盤基本信息的賦值。BIOS是來自內存 0x90080 處的數據,包括柱面數、磁頭數、扇區數等信息。不了解這些信息的可以看參考資料[3]。其實這里本來是個循環的,循環賦值所有硬盤信息進hd_info這個結構體數組,但是這里只考慮一個盤的情況,因此去掉了循環。最后BIOS加了16可以看上面內存的圖,硬盤1和硬盤2參數之間隔了16字節。這里是準備到下一個硬盤了,但是這里沒有了,所以不作考慮。

? ? 最終結果如下所示:

在這里插入圖片描述

🏆硬盤分區表的設置

📃硬盤分區表(Disk Partition Table,DPT)

? ? 硬盤分區表用于定義硬盤的分區信息。分區表存儲在硬盤的某個特定區域,通常是硬盤的第一個扇區,稱為主引導記錄(Master Boot Record, MBR),現代的分區表格式則主要是GPT(GUID Partition Table)。其作用主要是告訴操作系統硬盤上有多少個分區,每個分區的大小和位置

📃代碼實現

? ? 在linux0.11中的做法如下所示:

int sys_setup(void * BIOS)
{···hd[0].start_sect = 0;hd[0].nr_sects = hd_info[i].head*hd_info[i].sect*hd_info[i].cyl;struct buffer_head *bh = bread(0x300 + drive*5,0);struct partition *p = 0x1BE + (void *)bh->b_data;for (int i=1;i<5;i++,p++) {hd[i].start_sect = p->start_sect;hd[i].nr_sects = p->nr_sects;}brelse(bh);···
}

? ? 其實仔細看就能看出來,只是給一個新的結構體數組 hd 做賦值,而且這個結構體里面就兩個成員:start_sectnr_sects ,也就是開始扇區和總扇區數來記錄。循環里面一共4次,加上最開始初始化的,因此一共是五個分區,最后結果如下所示:

在這里插入圖片描述

? ? 這些信息從哪里獲取呢,就是在硬盤的第一個扇區的 0x1BE 偏移處,這里存儲著該硬盤的分區信息,只要把這個地方的數據拿到就 OK 了。

? ? 所以 bread 就是干這事的,從硬盤讀取數據:

struct buffer_head *bh = bread(0x300 + drive*5,0);

? ? 第一個參數 0x300 是第一塊硬盤的主設備號,就表示要讀取的塊設備是硬盤一。第二個參數 0 表示讀取第一個塊,一個塊為 1024 字節大小,也就是連續讀取硬盤開始處 0 ~ 1024 字節的數據。拿到這部分數據后,再取 0x1BE 偏移處,就得到了分區信息:

struct partition *p = 0x1BE + (void *)bh->b_data;

? ? 從硬盤的視角來看分區。0號塊本來是一個超級塊,可以參考這篇博客:linux0.11內核源碼修仙傳第十五章——文件系統,現在在里面多放一個分區信息。下面是示意圖:

在這里插入圖片描述

? ? 至于如何從硬盤中讀取指定位置(塊)的數據,也就是 bread 函數的內部實現,這部分略微復雜,先埋個坑日后再細聊。

🏆加載根文件系統

? ? 最后setup函數還有一部分:

int sys_setup(void * BIOS)
{···rd_load();		// 不用管mount_root();···
}

? ? 其中 rd_load 是當有 ramdisk 時,也就是虛擬內存盤,才會執行。虛擬內存盤是通過軟件將一部分內存(RAM)模擬為硬盤來使用的一種技術,此處當不存在,因此這行代碼無用。

? ? mount_root是加載根文件系統,有了它之后,操作系統才能從一個根開始找到所有存儲在硬盤中的文件,所以它是文件系統的基石,很重要:

void mount_root(void)
{int i,free;struct super_block * p;struct m_inode * mi;if (32 != sizeof (struct d_inode))panic("bad i-node size");for(i=0;i<NR_FILE;i++)file_table[i].f_count=0;if (MAJOR(ROOT_DEV) == 2) {printk("Insert root floppy and press ENTER");wait_for_keypress();}for(p = &super_block[0] ; p < &super_block[NR_SUPER] ; p++) {p->s_dev = 0;p->s_lock = 0;p->s_wait = NULL;}if (!(p=read_super(ROOT_DEV)))panic("Unable to mount root");if (!(mi=iget(ROOT_DEV,ROOT_INO)))panic("Unable to read root i-node");mi->i_count += 3 ;	/* NOTE! it is logically used 4 times, not 1 */p->s_isup = p->s_imount = mi;current->pwd = mi;current->root = mi;free=0;i=p->s_nzones;while (-- i >= 0)if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))free++;printk("%d/%d free blocks\n\r",free,p->s_nzones);free=0;i=p->s_ninodes+1;while (-- i >= 0)if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))free++;printk("%d/%d free inodes\n\r",free,p->s_ninodes);
}

📃mount_root 整體解讀

? ? 從整體上來說,mount_root 這個函數就是要把硬盤中的數據以文件系統的格式進行解讀,加載到內存中設計好的數據結構,這樣操作系統就可以通過內存中的數據,以文件系統的方式訪問硬盤中的一個個文件。首先回顧一下硬盤中的文件系統格式,區別于之前我們的博客里介紹的文件系統,哪個是ext2的格式,但是linux-0.11是MINIX文件系統,但是都是大同小異的,這里貼出來這個文件系統的格式:

在這里插入圖片描述

? ? 簡單看看這個文件系統:

引導塊:啟動區,當然不一定所有的硬盤都有啟動區,但我們還是得預留出這個位置,以保持格式的統一。
超級塊:用于描述整個文件系統的整體信息,我們看它的字段就知道了,有后面的 inode 數量,塊數量,第一個塊在哪里等信息。
inode位圖:inode的使用情況。
塊位圖:塊的使用情況。
i 結點:inode存放每個文件或目錄的元信息和索引信息。
塊:存放文件數據。

? ? 可是硬盤中憑什么就有了這些信息呢?這就是個雞生蛋蛋生雞的問題了。你可以先寫一個操作系統,然后給一個硬盤做某種文件系統類型的格式化,這樣你就得到一個有文件系統的硬盤了,有了這個硬盤,你的操作系統就可以成功啟動了。

📃內存中用于文件系統的數據結構

文件信息初始化

? ? 下面來逐步看,就只看第一個循環目前:

void mount_root(void)
{for(i=0;i<64;i++)file_table[i].f_count=0;···
}

? ? 這個循環就干了一件事,把 64 個 file_table 里的 f_count 清零。來看看具體這個 file_table,其表示進程所使用的文件,進程每使用一個文件都需要記錄在這里面,包括文件類型,inode索引信息,引用次數f_count,現在沒有被引用,所以先將其都置為0。來看看代碼里的具體實現:

struct file file_table[NR_FILE];struct file {unsigned short f_mode;unsigned short f_flags;unsigned short f_count;struct m_inode * f_inode;off_t f_pos;
};

? ? 來看一個file_table的使用案例。比如現在有如下的命令:echo "hello" > 0 。這個命令表示將字符串“hello”寫入到0號文件描述符。這個0號文件就是file_table[0]對應的文件。這個文件在硬盤哪里呢?注意到其中有個f_inode成員,通過這個即可找到indoe信息,inode里面包含了一個文件所需要的全部信息,包括文件大小,文件類型,文件所在硬盤號等。此事已在前面有所記載。

超級塊初始化

? ? 接著看這個函數后面的內容:

void mount_root(void)
{···for(p = &super_block[0] ; p < &super_block[8] ; p++) {p->s_dev = 0;p->s_lock = 0;p->s_wait = NULL;}···
}

? ? 這又是一個初始化的操作。super_block 就是我們之前講的超級塊,其作用也和之前的超級塊一樣,里面存的這個把設備的信息,通過這個超級塊就可以掌握整個設備的文件系統全局了。

s_dev :超級塊對應的設備號,置為 0 表示未使用。
s_lock :超級塊鎖,置為 0(未鎖定)
s_wait :等待隊列指針,置為 NULL

? ? 來看接下來的操作:

void mount_root(void)
{···if (!(p=read_super(0)))panic("Unable to mount root");···
}

? ? 接下來就是讀取硬盤的超級塊信息到內存中,read_super 函數就是讀取硬盤中的超級塊。

inode信息讀取

? ? 接下來讀取根目錄的inode信息。

int ROOT_DEV = 0;
#define ROOT_INO 1void mount_root(void)
{···if (!(mi=iget(ROOT_DEV, ROOT_INO)))panic("Unable to read root i-node");mi->i_count += 3 ;	// 邏輯上使用4次,初始為1···
}

? ? iget函數會獲取根inode,同時會增加inode引用次數,表示inode被使用。

? ? 接下來就是將 inode 設置當前進程(新建的進程1)的根目錄和工作目錄:

void mount_root(void)
{···p->s_isup = p->s_imount = mi;current->pwd = mi;		// 當前工作目錄(m_inode指針)current->root = mi;		// 根目錄(m_inode指針)···
}

記錄位圖信息

? ? 超級塊下面是塊位圖:

void mount_root(void)
{···free=0;i=p->s_nzones;		// 文件系統的總磁盤塊數while (-- i >= 0)if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))free++;···
}

? ? 首先來看看結構體p的內容:s_zmap 是位圖數組,每個元素指向一個頁,b_data是頁的內存地址。用于管理塊的占用狀態。i>>13 是因為一頁是 2^13 個塊,超出了就是其他頁,就是其他索引了。

? ? 其次來搞清楚set_bit函數的含義:

#define set_bit(bitnr,addr) ({ \
register int __res __asm__("ax"); \
__asm__("bt %2,%3;setb %%al":"=a" (__res):"a" (0),"r" (bitnr),"m" (*(addr))); \
__res; })

? ? 這個不是設置位!!!是檢查位的值。addr 的第 bitnr 位為 1,返回 1;否則返回 0。

? ? 8191換算成二進制是這個:1 1111 1111 1111,共13個1,這是取出低13位的值。為什么是13呢?那是因為在早期文件系統,如 MINIX。每個位圖頁管理8192(2^13)個塊。i&8191 表示位圖頁內偏移。合起來就是檢查每個塊對應的位是否為0,0就是空閑,就遞增free變量。通過 i >> 13i & 8191 分頁定位位圖中的位。

? ? free變量的含義就是統計文件系統中未被占用的磁盤塊(空閑塊)數量。

記錄inode位圖信息

? ? 最后就是inode位圖:

void mount_root(void)
{···free=0;i=p->s_ninodes+1;while (-- i >= 0)if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))free++;···
}

? ? 做法和目標同上面的塊位圖,只是i的值不一樣。

🎯總結

? ? 這篇博客就主要講了setup函數的內容,獲硬盤信息以及加載根文件。


📖參考資料

[1] linux源碼趣讀
[2] 一個64位操作系統的設計與實現
[3] 硬盤基本知識(磁道、扇區、柱面、磁頭數、簇、MBR、DBR)
[4] 分區表(Partition Table)是計算機硬盤驅動器上一個重要的數據結構

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/79489.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/79489.shtml
英文地址,請注明出處:http://en.pswp.cn/web/79489.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mobile自動化測試-appium webdriverio

WebdriverIO是一款支持mobile app和mobile web自動化測試框架&#xff0c;與appium集成&#xff0c;完成對mobile應用測試。支持ios 和android兩種平臺&#xff0c;且功能豐富&#xff0c;是mobile app自動化測試首選框架。且官方還提供了mobile 應用測試example代碼&#xff0…

Kubernetes排錯(十):常見網絡故障排查

通用排查思路 Kubernetes 集群內不同服務之間的網絡通信出現異常&#xff0c;表現為請求超時、連接失敗或響應緩慢&#xff0c;導致服務間依賴關系中斷&#xff0c;依賴服務的功能不可用或性能下降&#xff0c;甚至可能波及整個微服務架構&#xff0c;引發連鎖反應&#xff0c…

PyTorch 張量與自動微分操作

筆記 1 張量索引操作 import torch ? # 下標從左到右從0開始(0->第一個值), 從右到左從-1開始 # data[行下標, 列下標] # data[0軸下標, 1軸下標, 2軸下標] ? def dm01():# 創建張量torch.manual_seed(0)data torch.randint(low0, high10, size(4, 5))print(data->,…

接口的基礎定義與屬性約束

在 TypeScript 中&#xff0c;接口&#xff08;Interface&#xff09;是一個非常強大且常用的特性。接口定義了對象的結構&#xff0c;包括對象的屬性和方法&#xff0c;可以為對象提供類型檢查和約束。通過接口&#xff0c;我們可以清晰地描述一個對象應該具備哪些屬性和方法。…

高效全能PDF工具,支持OCR識別

軟件介紹 PDF XChange Editor是一款功能強大的PDF編輯工具&#xff0c;支持多種操作功能&#xff0c;不僅可編輯PDF內容與圖片&#xff0c;還具備OCR識別表單信息的能力&#xff0c;滿足多種場景下的需求。 軟件特點 這款PDF編輯器完全免費&#xff0c;用戶下載后直接…

OpenCV 中用于背景分割的一個類cv::bgsegm::BackgroundSubtractorGMG

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 cv::bgsegm::BackgroundSubtractorGMG 是 OpenCV 中用于背景分割的一個類&#xff0c;它實現了基于貝葉斯推理的背景建模算法&#xff08;Bayesi…

MongoDB知識框架

簡介&#xff1a;MongoDB 是一個基于分布式文件存儲的數據庫&#xff0c;屬于 NoSQL 數據庫產品&#xff0c;以下是其知識框架總結&#xff1a; 一、數據模型 文檔&#xff1a;MongoDB 中的數據以 BSON&#xff08;二進制形式的 JSON&#xff09;格式存儲在集合中&#xff0c;…

WEBSTORM前端 —— 第2章:CSS —— 第8節:網頁制作2(小兔鮮兒)

目錄 1.項目目錄 2.SEO 三大標簽 3.Favicon 圖標 4.版心 5.快捷導航(shortcut) 6.頭部(header) 7.底部(footer) 8.banner 9.banner – 圓點 10.新鮮好物(goods) 11.熱門品牌(brand) 12.生鮮(fresh) 13.最新專題(topic) 1.項目目錄 【xtx-pc】 ima…

1、RocketMQ 核心架構拆解

1. 為什么要使用消息隊列&#xff1f; 消息隊列&#xff08;MQ&#xff09;是分布式系統中不可或缺的中間件&#xff0c;主要解決系統間的解耦、異步和削峰填谷問題。 解耦&#xff1a;生產者和消費者通過消息隊列通信&#xff0c;彼此無需直接依賴&#xff0c;極大提升系統靈…

[Linux網絡_71] NAT技術 | 正反代理 | 網絡協議總結 | 五種IO模型

目錄 1.NAT技術 NAPT 2.NAT和代理服務器 3.網線通信各層協議總結 補充說明 4.五種 IO 模型 1.什么是IO&#xff1f;什么是高效的IO&#xff1f; 2.有那些IO的方式&#xff1f;這么多的方式&#xff0c;有那些是高效的&#xff1f; 異步 IO &#x1f3a3; 關鍵缺陷類比…

Unity基礎學習(八)時間相關內容Time

眾所周知&#xff0c;每一個游戲都會有自己的時間。這個時間可以是內部&#xff0c;從游戲開始的時間&#xff0c;也可以是外部真實的物理時間&#xff0c;時間相關內容 主要用于游戲中 參與位移計時 時間暫停等。那么我們今天就來看看Unity中和時間相關的內容。 Unity時間功能…

Java游戲服務器開發流水賬(1)游戲服務器的架構淺析

新項目立項停滯&#xff0c;頭大。近期讀老項目代碼看到Java&#xff0c;筆記記錄一下。 為什么要做服務器的架構 游戲服務器架構設計具有多方面的重要意義&#xff0c;它直接關系到游戲的性能、可擴展性、穩定性以及用戶體驗等關鍵因素 確保游戲的流暢運行 優化數據處理&a…

計算機視覺與深度學習 | 基于Transformer的低照度圖像增強技術

基于Transformer的低照度圖像增強技術通過結合Transformer的全局建模能力和傳統圖像增強理論(如Retinex),在保留顏色信息、抑制噪聲和平衡亮度方面展現出顯著優勢。以下是其核心原理、關鍵公式及典型代碼實現: 一、原理分析 1. 全局依賴建模與局部特征融合 Transformer的核…

Linux 文件目錄管理常用命令

pwd 顯示當前絕對路徑 cd 切換目錄 指令備注cd -回退cd …返回上一層cd ~切換到用戶主目錄 ls 列出目錄的內容 指令備注ls -a顯示當前目錄中的所有文件和目錄&#xff0c;包括隱藏文件ls -l以長格式顯示當前目錄中的文件和目錄ls -hl以人類可讀的方式顯示當前目錄中的文…

【Linux 系統調試】性能分析工具perf使用與調試方法

目錄 一、perf基本概念 1?. 事件類型? 2?. 低開銷高精度 3?. 工具定位? 二、安裝與基礎配置 1. 安裝方法 2. 啟用符號調試 三、perf工作原理 1. 數據采集機制 2. 硬件事件轉化流程 四、perf應用場景 1. 系統瓶頸定位 2. 鎖競爭優化 3. 緩存優化 五、perf高級…

嵌入式中屏幕的通信方式

LCD屏通信方式詳解 LCD屏&#xff08;液晶顯示屏&#xff09;的通信方式直接影響其數據傳輸效率、顯示刷新速度及硬件設計復雜度。根據應用場景和需求&#xff0c;LCD屏的通信方式主要分為以下三類&#xff0c;每種方式在協議類型、數據速率、硬件成本及適用場景上存在顯著差異…

【el-admin】el-admin關聯數據字典

數據字典使用 一、新增數據字典1、新增【圖書狀態】和【圖書類型】數據字典2、編輯字典值 二、代碼生成配置1、表單設置2、關聯字典3、驗證關聯數據字典 三、查詢操作1、模糊查詢2、按類別查詢&#xff08;下拉框&#xff09; 四、數據校驗 一、新增數據字典 1、新增【圖書狀態…

【Spring】Spring MVC筆記

文章目錄 一、SpringMVC簡介1、什么是MVC2、什么是SpringMVC3、SpringMVC的特點 二、HelloWorld1、開發環境2、創建maven工程a>添加web模塊b>打包方式&#xff1a;warc>引入依賴 3、配置web.xmla>默認配置方式b>擴展配置方式 4、創建請求控制器5、創建springMVC…

如何在大型項目中解決 VsCode 語言服務器崩潰的問題

在大型C/C項目中&#xff0c;VS Code的語言服務器&#xff08;如C/C擴展&#xff09;可能因內存不足或配置不當頻繁崩潰。本文結合系統資源分析與實戰技巧&#xff0c;提供一套完整的解決方案。 一、問題根源診斷 1.1 內存瓶頸分析 通過top命令查看系統資源使用情況&#xff…

LeetCode百題刷002摩爾投票法

遇到的問題都有解決的方案&#xff0c;希望我的博客可以為你提供一些幫助 圖片源自leetcode 題目&#xff1a;169. 多數元素 - 力扣&#xff08;LeetCode&#xff09; 一、排序法 題目要求需要找到多數值&#xff08;元素個數>n/2&#xff09;并返回這個值。一般會想到先…