主頁 > 作業系統 > Linux 0.11原始碼閱讀筆記-檔案IO流程

Linux 0.11原始碼閱讀筆記-檔案IO流程

2022-04-01 06:22:38 作業系統

檔案IO流程

用戶行程read、write在高速緩沖塊上讀寫資料,高速緩沖塊和塊設備交換資料,

  • 什么時機將磁盤塊資料讀到緩沖塊?
  • 什么時機將緩沖塊資料刷到磁盤塊?

image

函式呼叫關系

  • read/write(c庫函式,通過int 80呼叫sys_read/sys_write)
    • sys_read/sys_write
      • block_read/block_write
        • breada
          • getblk
            • sync_dev
          • ll_rw_block

sys_read與sys_write

代碼檔案:linux-0.11/fs/read_write.c

系統呼叫sys_read與sys_write是內核提供給用戶程式呼叫的IO介面,若IO設備是塊設備,底層分別呼叫block_read與block_write進行塊設備的讀寫,

sys_read

int sys_read(unsigned int fd,char * buf,int count)
{
	struct file * file;
	struct m_inode * inode;

    // 通過檔案描述符,在file表中找到file結構地址
	if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))
		return -EINVAL;
	if (!count)
		return 0;

	verify_area(buf,count);
	inode = file->f_inode;	// 通過file的f_inode訪問inode節點
    
    //判斷是什么設備:管道、字符設備、塊設備
    //如果是塊設備,呼叫block_read讀塊設備
	if (inode->i_pipe)
		return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;
	if (S_ISCHR(inode->i_mode))
		return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);
	if (S_ISBLK(inode->i_mode))
		return block_read(inode->i_zone[0],&file->f_pos,buf,count);

	if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {
		if (count+file->f_pos > inode->i_size)
			count = inode->i_size - file->f_pos;
		if (count<=0)
			return 0;
		return file_read(inode,file,buf,count);
	}
    
	printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);
	return -EINVAL;
}

sys_write

int sys_write(unsigned int fd,char * buf,int count)
{
	struct file * file;
	struct m_inode * inode;

	if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))
		return -EINVAL;
	if (!count)
		return 0;
    
    //判斷是什么設備:管道、字符設備、塊設備
    //如果是塊設備,呼叫block_write讀塊設備
	inode=file->f_inode;
	if (inode->i_pipe)
		return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;
	if (S_ISCHR(inode->i_mode))
		return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos);
	if (S_ISBLK(inode->i_mode))
		return block_write(inode->i_zone[0],&file->f_pos,buf,count);
	if (S_ISREG(inode->i_mode))
		return file_write(inode,file,buf,count);
    
	printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);
	return -EINVAL;
}

block_read與block_write

block_read與block_write負責塊設備的讀寫,他們底層呼叫breada函式獲取緩沖塊,然后在緩沖塊上讀寫資料,

block_write

代碼檔案:linux-0.11/fs/block_dev.c

int block_write(int dev, long * pos, char * buf, int count)
{
	int block = *pos >> BLOCK_SIZE_BITS;// pos所在檔案資料塊號
	int offset = *pos & (BLOCK_SIZE-1); // pos在資料塊中偏移值
	int chars;
	int written = 0;
	struct buffer_head * bh;			//指向當前寫緩沖塊
	register char * p;

    // 向緩沖塊中寫資料,通過getblk獲取緩沖塊,獲取緩沖塊的同時會讀取磁盤塊資料到緩沖塊
    // 資料量較多時,通過bread一次性快取3個磁盤塊資料到緩沖塊,減小磁盤IO次數
	while (count>0) {
		chars = BLOCK_SIZE - offset;
		if (chars > count)
			chars=count;
		if (chars == BLOCK_SIZE)
            //獲取高速緩沖塊,并建立其與磁盤塊的映射關系
			bh = getblk(dev,block);	
		else
            // 讀取的資料超過一個磁盤塊,呼叫breada讀多個塊
            // breada底層呼叫getblk快取3個連續磁盤塊的資料
			bh = breada(dev,block,block+1,block+2,-1);
		block++;
		if (!bh)
			return written?written:-EIO;
        
		p = offset + bh->b_data;
		offset = 0;
		*pos += chars;
		written += chars;
		count -= chars;
		while (chars-->0)
			*(p++) = get_fs_byte(buf++);
        
        //完成對緩沖塊的資料寫入后,設定緩沖塊的修改位dirt,然后釋放緩沖塊(參考計數減一)
		bh->b_dirt = 1;
		brelse(bh);
	}
	return written;
}

block_read

代碼檔案:linux-0.11/fs/block_dev.c

int block_read(int dev, unsigned long * pos, char * buf, int count)
{
	int block = *pos >> BLOCK_SIZE_BITS;
	int offset = *pos & (BLOCK_SIZE-1);
	int chars;
	int read = 0;
	struct buffer_head * bh;
	register char * p;

	while (count>0) {
		chars = BLOCK_SIZE-offset;
		if (chars > count)
			chars = count;
		if (!(bh = breada(dev,block,block+1,block+2,-1)))
			return read?read:-EIO;
		block++;
        
		p = offset + bh->b_data;
		offset = 0;
		*pos += chars;
		read += chars;
		count -= chars;
		while (chars-->0)
			put_fs_byte(*(p++),buf++);
        
		//完成對緩沖塊的資料讀取之后,釋放緩沖塊(參考計數減一)
        brelse(bh);
	}
	return read;
}

bread

代碼檔案:linux-0.11/fs/buffer.c

  • bread:塊讀取函式
  • breada:塊提前預讀函式
  • bread_page:頁塊讀取函式,一個記憶體頁通常為4k大小、磁盤塊通常為1k大小

bread、breada、bread_page三者功能相似,用法不同,三者均會呼叫getblk獲取緩沖塊,并呼叫ll_rw_block讀資料到緩沖塊,

struct buffer_head * bread(int dev,int block)
{
	struct buffer_head * bh;

	if (!(bh=getblk(dev,block)))
		panic("bread: getblk returned NULL\n");
	if (bh->b_uptodate)
		return bh;
    
    // 呼叫ll_rw_block讀磁盤塊資料到緩沖區
	ll_rw_block(READ,bh);
	wait_on_buffer(bh);
	if (bh->b_uptodate)
		return bh;
	brelse(bh);
	return NULL;
}

getblk

代碼檔案:linux-0.11/fs/buffer.c

bread系列函式通過getblk獲取緩沖塊,在必要的時候,會呼叫sync_dev函式將臟緩沖塊資料寫入磁盤,

getblk代碼邏輯復雜,需要對資源可用性進行復雜的檢查,資源不可用時,需要睡眠,被喚醒之后又要進行一些檢查判斷資源是否可用,復雜邏輯可以暫時不考慮,避免陷入代碼細節,

僅考慮getblk獲取空閑塊之后的代碼邏輯,getblk獲取可用緩沖塊后,若緩沖塊dirt位為1,表示緩沖塊有資料未同步到磁盤,getblk將呼叫sync_dev將資料同步到磁盤,然后占用該緩沖塊,

struct buffer_head * getblk(int dev,int block)
{
	struct buffer_head * tmp, * bh;

repeat:
    // 搜索hash表,如果指定塊已經在高速緩沖中,則回傳對應緩沖區頭指標,退出,
	if ((bh = get_hash_table(dev,block)))
		return bh;
    // 掃描空閑資料塊鏈表,尋找空閑緩沖區,
	tmp = free_list;
	do {
        // 如果該緩沖區正被使用(參考計數不等于0)
		if (tmp->b_count)
			continue;
        
        // 找到可用緩沖塊,且滿足一些條件
		if (!bh || BADNESS(tmp)<BADNESS(bh)) {
			bh = tmp;
			if (!BADNESS(tmp))
				break;
		}
/* and repeat until we find something good */
	} while ((tmp = tmp->b_next_free) != free_list);
    
    // 沒有可用緩沖塊,則睡眠等待有空閑緩沖塊可用,
    // 當有空閑緩沖塊可用時本行程會被的喚醒,
	if (!bh) {
		sleep_on(&buffer_wait); //睡眠在緩沖區上
		goto repeat;
	}
   	
    //等待緩沖區解鎖?
	wait_on_buffer(bh);
	if (bh->b_count)
		goto repeat;
  	
    // 分配到的緩沖塊dirt位為1(表示有資料未同步到磁盤)
    // 呼叫sync_dev將資料同步到磁盤,并睡眠在該緩沖塊上
	while (bh->b_dirt) {
		sync_dev(bh->b_dev);
		wait_on_buffer(bh);
		if (bh->b_count)
			goto repeat;
	}
/* NOTE!! While we slept waiting for this block, somebody else might */
/* already have added "this" block to the cache. check it */
	if (find_buffer(dev,block))
		goto repeat;
/* OK, FINALLY we know that this buffer is the only one of it's kind, */
/* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */
    
    // 對空閑緩沖塊的處理
    // 占用空閑緩沖塊,置參考計數為1,復位修改標志和有效(更新)標志,
	bh->b_count=1;
	bh->b_dirt=0;
	bh->b_uptodate=0;
    // 從原hash佇列和空閑佇列塊鏈表中移出該緩沖區頭,根據此新的設備號和塊號重新插入空閑鏈表和hash佇列
    // 讓該緩沖區用于指定設備和其上的指定塊,
    // 根據此新的設備號和塊號重新哈希,并插入回應的hash佇列
	remove_from_queues(bh);
	bh->b_dev=dev;
	bh->b_blocknr=block; //加鎖
	insert_into_queues(bh);
	return bh;
}

sync_dev

代碼檔案:linux-0.11/fs/buffer.c

呼叫ll_rw_block將緩沖塊內資料寫入磁盤,getblk管理緩沖塊時,若其它行程需要某緩沖塊,且緩沖塊具有臟(dirt位為1)資料,呼叫sync_dev將資料寫入磁盤,

int sync_dev(int dev)
{
	int i;
	struct buffer_head * bh;

	bh = start_buffer;
	for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
		if (bh->b_dev != dev)
			continue;
		wait_on_buffer(bh);
		if (bh->b_dev == dev && bh->b_dirt)
            // 呼叫ll_rw_block寫緩沖區資料到磁盤塊
			ll_rw_block(WRITE,bh);
	}

	bh = start_buffer;
	for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
		if (bh->b_dev != dev)
			continue;
		wait_on_buffer(bh);
		if (bh->b_dev == dev && bh->b_dirt)
			ll_rw_block(WRITE,bh);
	}
	return 0;
}

ll_rw_block

代碼檔案:linux-0.11/kernel/blk_drv/ll_rw_blk.c

將緩沖塊的資料寫入磁盤塊,獲將磁盤塊資料讀入緩沖塊,底層通過設備請求佇列完成讀寫,

void ll_rw_block(int rw, struct buffer_head * bh)
{
	unsigned int major;

	if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||
	!(blk_dev[major].request_fn)) {
		printk("Trying to read nonexistent block-device\n\r");
		return;
	}
    
    // 將讀寫請求加入設備請求佇列
	make_request(major,rw,bh);
}

設備中斷處理程式

代碼檔案:linux-0.11/kernel/blk_drv/hd.c

  • 讀完成中斷處理程式

設備完成讀扇區資料后,發出讀中斷,讀中斷處理程式read_intr執行,若當前讀請求還有資料要讀,則繼續完成當前請求的資料讀,因為,一次讀請求可能讀若干連續扇區資料,磁盤每次只能寫讀一個扇區資料,完成一次讀請求的所有資料讀之后,將呼叫do_hd_request處理下一個寫請求,

static void read_intr(void)
{
	if (win_result()) {
		bad_rw_intr();
		do_hd_request();
		return;
	}
	port_read(HD_DATA,CURRENT->buffer,256);
	CURRENT->errors = 0;
	CURRENT->buffer += 512;
	CURRENT->sector++;
	if (--CURRENT->nr_sectors) {
		do_hd = &read_intr;
		return;
	}
	end_request(1);
	do_hd_request();
}
  • 寫完成中斷處理程式

與寫完成中斷處理程式程序類似,

static void write_intr(void)
{
	if (win_result()) {
		bad_rw_intr();
		do_hd_request(); //處理下一個請求
		return;
	}
	if (--CURRENT->nr_sectors) {
		CURRENT->sector++;
		CURRENT->buffer += 512;
		do_hd = &write_intr;
		port_write(HD_DATA,CURRENT->buffer,256);
		return;
	}
	end_request(1);
	do_hd_request();
}
  • 處理讀寫佇列請求

處理設備請求佇列的讀寫請求,設備中斷處理程式不斷呼叫do_hd_request處理請求佇列,直到請求佇列為空,

void do_hd_request(void)
{
	int i,r = 0;
	unsigned int block,dev;
	unsigned int sec,head,cyl;
	unsigned int nsect;

	INIT_REQUEST;
	dev = MINOR(CURRENT->dev);
	block = CURRENT->sector;
	if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) {
		end_request(0);
		goto repeat;
	}
	block += hd[dev].start_sect;
	dev /= 5;
	__asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
		"r" (hd_info[dev].sect));
	__asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
		"r" (hd_info[dev].head));
	sec++;
	nsect = CURRENT->nr_sectors;
	if (reset) {
		reset = 0;
		recalibrate = 1;
		reset_hd(CURRENT_DEV);
		return;
	}
	if (recalibrate) {
		recalibrate = 0;
		hd_out(dev,hd_info[CURRENT_DEV].sect,0,0,0,
			WIN_RESTORE,&recal_intr);
		return;
	}	
	if (CURRENT->cmd == WRITE) {
		hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
		for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
			/* nothing */ ;
		if (!r) {
			bad_rw_intr();
			goto repeat;
		}
		port_write(HD_DATA,CURRENT->buffer,256);
	} else if (CURRENT->cmd == READ) {
		hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
	} else
		panic("unknown hd-command");
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/453731.html

標籤:Linux

上一篇:Centos 7 上安裝 jdk 及問題小記

下一篇:Linux之export命令

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more