通過文件進行磁盤操作入口
// 在fs/read_write.c中
int sys_write(int fd, const char* buf, int count)
{struct file *file = current->filp[fd];struct m_inode *inode = file->inode;if (S_ISREG(inode->i_mode))return file_write(inode, file, buf, count);
}
-
進程控制塊(PCB):
-
current
是指向當前進程控制塊(PCB)的指針。PCB 包含了進程的所有信息,包括打開的文件列表。
-
-
文件指針數組(filp):
-
current->filp[fd]
訪問當前進程的文件指針數組,獲取與文件描述符fd
關聯的file
結構。
-
-
文件結構(file):
-
struct file
包含了打開文件的所有信息,包括指向inode
的指針。
-
-
inode 結構:
-
struct m_inode
是文件的元數據結構,包含了文件的屬性(如權限、大小、數據塊位置等)。 -
inode->i_mode
包含了文件的類型和權限信息。
-
-
文件寫入操作:
-
file_write(inode, file, buf, count)
是實際執行文件寫入操作的函數,它將buf
中的count
字節數據寫入到由inode
和file
描述的文件中。
-
-
文件類型檢查:
-
S_ISREG(inode->i_mode)
檢查inode
表示的是否是一個普通文件。如果是,才執行寫入操作。
-
-
數據流和控制流:
-
圖中展示了從
write(fd)
調用開始,通過 PCB 和文件表,最終訪問到inode
和數據盤塊的過程。
-
File_write
工作流程分析
-
確定要寫入的字符段:
-
首先,需要確定要寫入文件的字符段。這通常由文件的讀寫指針決定,該指針指示了文件中的當前位置。通過修改這個指針(例如使用
fseek
),可以改變寫入的起始位置,然后加上要寫入的字符數量(count
)來確定結束位置。
-
-
找到要寫的盤塊號:
-
確定了要寫入的字符段后,下一步是找到這些字符對應的磁盤塊號。這是通過文件的
inode
結構來實現的,inode
包含了文件的元數據,包括文件數據塊的位置信息。
-
-
形成寫入請求并執行寫入:
-
使用找到的盤塊號和要寫入的數據(
buf
),形成一個寫入請求(request
),然后將這個請求放入磁盤調度算法(如電梯算法)中進行處理,以優化磁盤I/O操作。
-
函數參數解釋:
-
inode
:-
這是一個指向文件的
inode
結構的指針。inode
包含了文件的元數據,如文件類型、權限、所有者、文件大小以及指向文件數據塊的指針等。在寫入操作中,inode
用于查找文件數據塊的位置。
-
-
file
:-
這是一個指向文件結構的指針。文件結構包含了打開文件的所有信息,包括文件狀態標志、文件的讀寫指針等。在寫入操作中,
file
結構用于獲取文件的當前位置和狀態。
-
-
buf
:-
這是一個指向要寫入文件的數據緩沖區的指針。
buf
包含了實際要寫入文件的數據。
-
-
count
:-
這是一個整數,表示要寫入文件的字節數。
count
與buf
參數一起,指定了要寫入文件的數據量。
-
實現
以下是帶有詳細注解的 file_write
函數代碼,解釋了每一部分的功能和作用:
// file_write函數用于將數據從用戶緩沖區寫入到文件對應的磁盤塊中
int file_write(struct m_inode *inode, struct file *filp, char *buf, int count)
{off_t pos; // 定義一個變量pos,用于記錄寫入文件的起始位置
?// 根據文件的讀寫標志(O_APPEND)和當前文件位置(filp->f_pos)確定寫入的起始位置if (filp->f_flags & O_APPEND)pos = inode->i_size; // 如果設置了追加標志,則從文件末尾開始寫入elsepos = filp->f_pos; // 否則,從文件當前位置開始寫入
?// 當還有字節需要寫入時,繼續循環while (i < count) {block = create_block(inode, pos / BLOCK_SIZE); // 創建或分配一個新的磁盤塊bh = bread(inode->i_dev, block); // 讀取指定的磁盤塊到內存中,并將塊地址存儲在bh中
?int c = pos % BLOCK_SIZE; // 計算當前塊內的偏移char *pc = c + bh->b_data; // 計算內存中塊數據區域的起始地址bh->b_dirt = 1; // 標記塊為臟塊,表示該塊已被修改,需要寫回磁盤c = BLOCK_SIZE - c; // 計算剩余需要寫入的字節數pos += c; // 更新文件位置
?// 將用戶緩沖區buf中的數據逐字節寫入到內存中的塊數據區域bh->b_datawhile (c-- > 0)*(pc++) = get_fs_byte(buf++);
?brelse(bh); // 釋放緩沖區,將其返回到緩沖區管理中}
?filp->f_pos = pos; // 更新文件的當前位置
}
-
確定寫入位置:
-
通過檢查文件的讀寫標志和當前文件位置,確定寫入操作的起始位置。
-
-
循環寫入數據:
-
使用
while
循環,根據要寫入的字節數count
,逐塊寫入數據。
-
-
創建和讀取磁盤塊:
-
create_block
函數用于創建或分配一個新的磁盤塊。 -
bread
函數用于讀取指定的磁盤塊到內存中,并將塊地址存儲在bh
(緩沖頭)中。
-
-
計算塊內偏移和剩余字節數:
-
計算當前塊內的偏移
c
和剩余需要寫入的字節數。
-
-
寫入數據:
-
將用戶緩沖區
buf
中的數據逐字節寫入到內存中的塊數據區域bh->b_data
。
-
-
標記塊為臟塊:
-
將
bh->b_dirt
設置為 1,表示該塊已被修改,需要寫回磁盤。
-
-
釋放緩沖區:
-
使用
brelse
函數釋放緩沖區,將其返回到緩沖區管理中。
-
-
更新文件位置:
-
更新文件的當前位置
filp->f_pos
,以便后續操作可以從正確的位置開始。
-
通過這些步驟,file_write
函數實現了將用戶數據寫入文件的完整過程,包括確定寫入位置、創建和讀取磁盤塊、寫入數據、標記塊為臟塊以及更新文件位置等。
Create_block
圖中展示了 create_block
函數和 _bmap
函數的代碼,這些函數用于在文件系統中創建和映射磁盤塊。以下是提取的代碼和注解:
提取的代碼:
// create_block函數用于根據inode和塊號分配或創建一個新的磁盤塊
int create_block(struct m_inode *inode, int block)
{while (i < count) {block = create_block(inode, pos / BLOCK_SIZE);bh = bread(inode->i_dev, block);}
?int _bmap(m_inode *inode, int block, int create){if (block < 7) {if (create && !inode->i_zone[block]) {inode->i_zone[block] = new_block(inode->i_dev);inode->i_ctime = CURRENT_TIME;inode->i_dirt = 1;}return inode->i_zone[block];}block -= 7;if (block < 512) {bh = bread(inode->i_dev, inode->i_zone[7]);return (bh->b_data)[block];}}
}
代碼注解:
-
create_block函數:
-
這個函數用于根據給定的
inode
和塊號block
分配或創建一個新的磁盤塊。 -
它通過調用
_bmap
函數來實現塊的映射和創建。
-
-
_bmap函數:
-
這是一個輔助函數,用于處理塊號的映射。
-
它首先檢查塊號是否小于7,這7個塊號直接存儲在
inode
的i_zone
數組中。 -
如果塊號小于7,并且需要創建新塊(
create
為真且當前塊號未分配),則調用new_block
函數分配新塊,更新inode
的i_ctime
和i_dirt
標志。 -
如果塊號大于等于7且小于512,表示這是一個一重間接塊,需要讀取間接塊表并找到對應的塊號。
-
-
new_block函數:
-
這個函數用于分配一個新的磁盤塊。具體實現未在圖中展示,但通常涉及查找空閑塊、分配塊號等操作。
-
-
bread函數:
-
這個函數用于讀取指定的磁盤塊到內存中。它將塊地址存儲在
bh
(緩沖頭)中,以便后續操作。
-
總結:
這段代碼實現了文件系統中磁盤塊的分配和映射機制。通過 create_block
和 _bmap
函數,系統可以根據文件的 inode
和塊號動態分配和映射磁盤塊。這種機制支持文件的動態增長和高效的磁盤空間管理。
通過間接塊表(如一重間接和二重間接塊表),文件系統還可以支持非常大的文件,即使單個文件的數據量超過了直接塊表所能表示的范圍。
m_inode
圖中展示了與設備文件的 inode
結構相關的代碼,以及如何通過 sys_open
函數打開設備文件。以下是提取的代碼和注解:
提取的代碼:
// m_inode結構體定義,用于表示設備文件的inode
struct m_inode {unsigned short i_mode; // 文件的類型和屬性unsigned short i_zone[9]; // 指向文件內容數據塊struct task_struct *i_wait; // 多個進程共享的打開這個inode,有的進程等待...unsigned short i_count; // unsigned char i_lock; // unsigned char i_dirt; //
};
?
// sys_open函數用于打開文件或設備
int sys_open(const char* filename, int flag)
{if (S_ISCHR(inode->i_mode)) { // 檢查是否為字符設備文件if (MAJOR(inode->i_zone[0]) == 4) { // 檢查設備文件類型current->tty = MINOR(inode->i_zone[0]); // 設置當前進程的tty}}
}
?
// 宏定義,用于從設備號中提取主設備號和次設備號
#define MAJOR(a) ((((unsigned)(a)) >> 8)) // 取高字節
#define MINOR(a) ((a) & 0xff) // 取低字節
代碼注解:
-
m_inode結構體:
-
i_mode
:表示文件的類型和屬性,例如普通文件、目錄、字符設備等。 -
i_zone
:一個數組,用于存儲指向文件內容數據塊的指針或設備號等信息。 -
i_wait
:指向等待該inode的進程的task_struct結構體。 -
i_count
:表示當前有多少個進程打開了該inode。 -
i_lock
:用于控制對inode的訪問,防止并發訪問導致的問題。 -
i_dirt
:表示inode是否被修改,需要寫回磁盤。
-
-
sys_open函數:
-
該函數用于打開文件或設備。
-
S_ISCHR(inode->i_mode)
:檢查文件是否為字符設備文件。 -
MAJOR(inode->i_zone[0])
和MINOR(inode->i_zone[0])
:從設備號中提取主設備號和次設備號。 -
current->tty
:設置當前進程的tty(終端設備)。
-
-
宏定義:
-
MAJOR(a)
:從設備號中提取主設備號(高字節)。 -
MINOR(a)
:從設備號中提取次設備號(低字節)。
-
總結:
這段代碼展示了如何使用 m_inode
結構體來表示設備文件的inode,并演示了如何通過 sys_open
函數打開設備文件。
文件視圖的兩條路
讀寫磁盤的過程:
-
用戶發起文件操作
-
用戶通過
write
或read
系統調用操作文件。 -
傳遞參數:文件描述符(FD)、內存緩沖區(buff)、操作字節數(count)。
-
-
系統調用轉換
-
write
調用內核中的sys_write
,read
調用sys_read
。 -
需要獲取文件的
inode
(索引節點)信息。
-
-
計算目標磁盤塊
-
通過文件結構
file
獲取文件指針f_pos
,確定字符流中的位置。 -
根據
inode
及索引結構,計算出對應的磁盤塊號(block)。 -
采用直接索引、一級索引、二級索引等方式解析出數據塊。
-
-
磁盤操作
-
讀操作:調用
bread()
讀取磁盤數據到緩存,再復制到用戶緩沖區。 -
寫操作:調用
bwrite()
將用戶緩沖區數據寫入磁盤緩存,并同步到磁盤。
-
-
磁盤調度與完成
-
磁盤請求加入電梯調度隊列。
-
設備驅動完成磁盤讀寫,系統中斷通知完成。
-
若是寫操作,需更新
f_pos
以維護文件流的連續性。
-
輸出到顯示器的過程:
-
用戶發起輸出請求
-
例如
printf()
、write(1, buff, count)
,其中1
代表標準輸出(stdout)。
-
-
系統調用轉換
-
進入內核,調用
sys_write
處理。 -
識別文件描述符
1
代表標準輸出(顯示器)。
-
-
數據傳輸到終端設備
-
由于 stdout 不是磁盤文件,而是字符設備,直接走
tty
終端驅動。 -
終端驅動
tty_write
處理輸出,將數據放入tty
緩沖區。
-
-
驅動程序與硬件交互
-
tty
設備驅動調用console_driver
,執行putc()
逐字符輸出到屏幕。 -
可能涉及
VGA
、framebuffer
或UART
串口等設備。
-
-
顯示完成
-
終端驅動完成數據輸出,可能觸發屏幕刷新或回顯。
-
若是行緩沖模式,遇到
\n
或緩沖區滿才實際輸出。
-
對比總結:
-
讀寫磁盤涉及塊設備(block device),數據按塊存取,可能需要磁盤調度與緩沖管理。
-
輸出到顯示器涉及字符設備(char device),數據按字符流傳輸,即時性更強。
-
兩者都通過系統調用進入內核,但處理方式不同,磁盤用
bread/bwrite
,顯示器用tty_write
。