Linux Storage入门学习
前言
本文大量代碼基于linux 0.11,因為早期linux的版本更加適合初學者入門。雖然代碼比較早,但是不妨礙我們學習Linux Storage的精髓。
一、hello world
1.1 Demo
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h>int main(int argc, char *argv[]) {printf("pid = %d\n", getpid());int fd1,fd2;char s[] = "hello world\n";//打開文件,拿到fdfd1 = open("/tmp/1.txt", O_RDWR | O_CREAT);printf("fd1 = %d\n", fd1);//寫入write(fd1, s, sizeof(s));close(fd1);fd2 = open("/tmp/2.txt", O_RDWR | O_CREAT);printf("fd2 = %d\n", fd2);//打開文件,拿到fdfd1 = open("/tmp/1.txt", O_RDWR);printf("fd1 = %d\n", fd1);char buffer[80];//讀取read(fd1, buffer, sizeof(buffer));//關閉fdprintf("%s", buffer);getchar();//暫停程序close(fd1);close(fd2);return 0; }運行結果
pid = 14378 fd1 = 3 fd2 = 3 fd1 = 4 hello world1.1 fd只是一個數字,代表數字和文件之前的一個映射關系
查看/proc/14378/fd,可以看到映射關系
dr-x------ 2 wangbinhong tctnb 0 3月 14 16:27 . dr-xr-xr-x 9 wangbinhong tctnb 0 3月 14 16:26 .. lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 0 -> /dev/pts/19 //stdin lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 1 -> /dev/pts/19 //stdout lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 2 -> /dev/pts/19 //stderr lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 3 -> /tmp/2.txt lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 4 -> /tmp/1.txt程序中的4是數字,/proc/14378/fd/4是一個文件,鏈接到/tmp/1.txt,/proc/14378/fd/4只存在內存中,不存在硬盤中,程序中的數字4不是指向/proc/14378/fd/4文件
二、數字fd代表什么
2.1 task_struct
每一個進程在內核中的有一個task_struct的結構體,結構體中有一個file指針數組filp,fd代表filp這個數組的下標,fd = 4 就代表filp[4]指向的file結構體
struct task_struct {...struct file * filp[NR_OPEN];... }2.2 file
struct file {unsigned short f_mode;//文件的類型和屬性unsigned short f_flags;//文件打開的標志unsigned short f_count;//關聯的fd的個數struct m_inode * f_inode;//file的真實實現off_t f_pos;//文件當前的讀寫指針,讀到哪里了。 }2.3 file_table
內核中還有一個全局的file_table,是file數組,保存所有file結構體。
struct file file_table[NR_FILE];//系統級的一個file table2.4 關聯關系
兩個細節
dup:同進程兩個fd指向同一個file
fork:兩個進程的兩個fd指向同一個file
下面是新的Kernel關系圖
2.5 Binder傳輸fd
Binder傳輸fd,兩個進程的不同fd指向了同一個file,享有相同的file offset和file status flag.
三、以pipe為例:一切皆文件
早期的錯誤想法
硬件驅動通過設備文件和用戶空間的應用程序通信,是通過將驅動的信息寫進設備文件,然后應用程序讀取設備文件的內容。
3.1 pipe初始化
3.1.1 sys_pipe
系統調用pipe的實現,會返回兩個fd給用戶空間
int sys_pipe(unsigned long * fildes)//系統調用生成一對pipe {struct m_inode * inode;struct file * f[2];int fd[2];int i,j;j=0;for(i=0;j<2 && i<NR_FILE;i++)if (!file_table[i].f_count)//找到空閑的file(f[j++]=i+file_table)->f_count++;//將空閑的file的f_count+1,并保存這兩個file結構體if (j==1)//只找到一個f[0]->f_count=0;//將第一file重置,清空if (j<2)return -1;//沒找到一隊,反正就是失敗j=0;for(i=0;j<2 && i<NR_OPEN;i++)//將兩個file的指針分別保存到current->filp[i]的空閑處if (!current->filp[i]) {current->filp[ fd[j]=i ] = f[j];j++;}if (j==1)current->filp[fd[0]]=NULL;if (j<2) {f[0]->f_count=f[1]->f_count=0;return -1;}//和上面邏輯類似if (!(inode=get_pipe_inode())) {//詳見3.1.2 獲得一個pipe inodecurrent->filp[fd[0]] =current->filp[fd[1]] = NULL;f[0]->f_count = f[1]->f_count = 0;return -1;}f[0]->f_inode = f[1]->f_inode = inode;//讓兩個file結構體的f_inode指向pipe inodef[0]->f_pos = f[1]->f_pos = 0;//重置讀寫指針f[0]->f_mode = 1; /* read */f[1]->f_mode = 2; /* write */put_fs_long(fd[0],0+fildes);//將fd0數值返回給用戶空間put_fs_long(fd[1],1+fildes);//將fd1數值返回給用戶空間return 0; }3.1.2 get_pipe_inode
創建一個m_inode結構體
將m_inode的i_size指向一塊4096B的緩沖區
設置m_inode的i_pipe為1,標識這個m_inode為pipe inode
3.1.3 get_free_page
申請一個物理頁,并返回內核空間的地址
unsigned long get_free_page(void) { register unsigned long __res asm("ax");__asm__("std ; repne ; scasb\n\t""jne 1f\n\t""movb $1,1(%%edi)\n\t""sall $12,%%ecx\n\t""addl %2,%%ecx\n\t""movl %%ecx,%%edx\n\t""movl $1024,%%ecx\n\t""leal 4092(%%edx),%%edi\n\t""rep ; stosl\n\t""movl %%edx,%%eax\n""1:":"=a" (__res):"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),"D" (mem_map+PAGING_PAGES-1):"di","cx","dx"); return __res; }3.2 讀pipe
3.2.1 sys_read
根據inode->i_pip,將sys_read變成read_pipe。
int sys_read(unsigned int fd,char * buf,int count)//文件讀的系統調用,fd->file->inode->數據塊 {struct file * file;struct m_inode * inode;if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))return -EINVAL;if (!count)return 0;verify_area(buf,count);inode = file->f_inode;if (inode->i_pipe)//pipereturn (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;//3.2.2if (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; }3.2.2 read_pipe
將緩沖區的數據讀到用戶空間char,讀count字節
int read_pipe(struct m_inode * inode, char * buf, int count)//讀取pipe {int chars, size, read = 0;while (count>0) {while (!(size=PIPE_SIZE(*inode))) {//如果發現沒有內容wake_up(&inode->i_wait);//喚醒寫端if (inode->i_count != 2) /* are there any writers? *///沒有寫端return read;sleep_on(&inode->i_wait);//沒有內容就睡眠}chars = PAGE_SIZE-PIPE_TAIL(*inode);//判斷尾部的數據if (chars > count)chars = count;if (chars > size)chars = size;count -= chars;read += chars;size = PIPE_TAIL(*inode);//頭部開始讀的指針PIPE_TAIL(*inode) += chars;PIPE_TAIL(*inode) &= (PAGE_SIZE-1);while (chars-->0)put_fs_byte(((char *)inode->i_size)[size++],buf++);}wake_up(&inode->i_wait);return read; }3.3 寫pipe
3.3.1 sys_write
根據inode->i_pip,將sys_write變成write_pipe。
int sys_write(unsigned int fd,char * buf,int count)//文件寫的系統調用,fd->file->inode->數據塊 {struct file * file;struct m_inode * inode;if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))return -EINVAL;if (!count)return 0;inode=file->f_inode;if (inode->i_pipe)return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;//3.3.2if (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; }3.3.2 write_pipe
將用戶空間char對應的數據寫到緩沖區,寫入count字節
int write_pipe(struct m_inode * inode, char * buf, int count)//寫pipe的實現 {int chars, size, written = 0;while (count>0) {while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//如果寫滿了,size為0wake_up(&inode->i_wait);//喚醒讀端if (inode->i_count != 2) { /* no readers *///沒有讀者直接返回current->signal |= (1<<(SIGPIPE-1));return written?written:-1;}sleep_on(&inode->i_wait);//寫端休眠}chars = PAGE_SIZE-PIPE_HEAD(*inode);//計算管道頭部到緩沖區末端的空閑字節數 4098if (chars > count)chars = count;if (chars > size)chars = size;count -= chars;written += chars;size = PIPE_HEAD(*inode);//當前的頭指正PIPE_HEAD(*inode) += chars;PIPE_HEAD(*inode) &= (PAGE_SIZE-1);while (chars-->0)((char *)inode->i_size)[size++]=get_fs_byte(buf++);//一個寫字符到管道}wake_up(&inode->i_wait);//寫完喚醒讀端return written; }3.4 pipe讀寫指針
其實pipe的讀寫指針并沒有保存在file結構體,而是保存在m_inode。
如果緩沖區滿了,讀端一會不讀,會導致寫端的進程sleep。
整個讀寫的過程,并沒有通過鎖來控制,而是通過ringbuffer來實現,有興趣的可以自己研究。
3.5 思考一個問題
父進程創建一對pipe的fd1 fd2
子進程通過fork復制父進程的pipe fd1 fd2
父進程關閉讀的fd1
子進程關閉寫的fd2
父子進程就可以通過pipe進行跨進程通信
3.6 Linux的改進
當文件類型越來越多的時候,用file_operations結構體代替大量if else
file_operations中保存read write的函數指針
我覺得這樣子理解更加合適:一切皆文件接口
四、普通文件
4.1重要數據結構
4.1.1 m_inode
file中f_inode指向就是m_inode,也就是file對應的真實實現。
i_dev:代表塊設備號
i_zone[9]:代表數據塊號
4.1.2 buffer_head
b_dev設備號
b_blocknr數據塊號
b_data指向一塊內存
所有buffer_head會被存放在一個hash表中
可以調用下面兩個接口,完成數據塊的讀寫,這背后的實現就要看塊設備驅動怎么實現的。
先記住,buffer_head(設備號+塊號+內存地址)+讀寫指令 可以完成一次信息的交換。
ll_rw_block(READ,bh); ll_rw_block(WRITE,bh);4.2 讀文件
4.2.1 file_read
根據(filp->f_pos)/BLOCK_SIZE計算對應的塊號nr
根據inode->i_dev和nr調用bread獲得buffer_head
調用ll_rw_block(READ,bh)請求數據塊的數據
將buffer_head中b_data拷貝到用戶空間的buf
4.2.2 bread
根據設備號,數據塊號,創建一個buffer_head
/** bread() reads a specified block and returns the buffer that contains* it. It returns NULL if the block was unreadable.*/ struct buffer_head * bread(int dev,int block)//從dev,block獲得buffer_head,一般用這個就可以 {struct buffer_head * bh;if (!(bh=getblk(dev,block)))//拿一塊空閑的buffer_headpanic("bread: getblk returned NULL\n");if (bh->b_uptodate)return bh;ll_rw_block(READ,bh);//將硬件的數據讀取到buffer_headwait_on_buffer(bh);if (bh->b_uptodate)return bh;brelse(bh);//釋放鎖return NULL; }4.3 寫文件
4.3.1 file_write
根據(filp->f_pos)/BLOCK_SIZE計算對應的塊號block,如果文件不夠大,需要擴容。
根據inode->i_dev和block調用bread獲得buffer_head
將用戶空間的buf拷貝到buffer_head中b_data。
4.3.2 sys_sync
ll_rw_block將buffer_head的臟數據同步到塊設備
int sys_sync(void)//系統調用,同步塊設備和內存高速緩存中數據 {int i;struct buffer_head * bh;sync_inodes(); /* write out inodes into buffers *///將修改的inode數據寫入到buffer_head.bh = start_buffer;for (i=0 ; i<NR_BUFFERS ; i++,bh++) {wait_on_buffer(bh);if (bh->b_dirt)ll_rw_block(WRITE,bh);//真的寫到塊設備中}return 0; }五、塊設備
5.1 內部結構
5.2 mount("dev/block/sda0","/sdcard")
第一步:從dev/block/sda0中讀取第0塊inode塊上的數據,讀取第一個d_inode的數據,并構建內存中的m_inode。
第二步:將m_inode的i_num和"sdcard"按照dir_entry的結構體存放在"/"目錄對應m_inode指向的i_zone數據區域
5.3 int fd = open("/sdcard/1.txt")
第一步:找到m_inode("sdcard")
讀取"/"對應的m_inode("/")的 i_zone[9]的數據到內存中
根據"sdcard"得到inode號,拿到"sdcard"對應的m_inode("sdcard"),m_inode已經在mount中創建。
第二步:創建m_inode("1.txt")
讀取m_inode("sdcard")的 i_zone[9]的數據到內存中
根據"1.txt",拿到1.txt文件對應的d_inode的號
計算d_inode號找到對應的d_inode結構體存放在塊設備的塊號,塊號=inode/一個塊最多存放的d_inode結構體
讀取的塊號對應數據到內存中,讀取對應的d_inode數據,構建m_inode("sdcard")
第三步:將fd指向file指向m_inode
創建file指向m_inode("1.txt")
file[fd]指向file
返回fd
從此形成fd->file->m_inode的對應關系,write read close 都可以對應的轉化成file的操作,對應的m_inode的操作
六、目前Linux的架構
構建了一個VFS層,虛擬文件系統,各類文件系統可以更好的兼容,EXT4,F2FS
文件系統和塊設備的數據交互,用BIO代替了buffer_head
新增了Block Layer層對BIO進行合并調度
? 回復「?籃球的大肚子」進入技術群聊
回復「1024」獲取1000G學習資料
總結
以上是生活随笔為你收集整理的Linux Storage入门学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python 异步定时任务
- 下一篇: C#照片合成PDF_ PDF合成或拆分P