Linux Storage入门学习
前言
本文大量代碼基于linux 0.11,因?yàn)樵缙趌inux的版本更加適合初學(xué)者入門。雖然代碼比較早,但是不妨礙我們學(xué)習(xí)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));//關(guān)閉fdprintf("%s", buffer);getchar();//暫停程序close(fd1);close(fd2);return 0; }運(yùn)行結(jié)果
pid = 14378 fd1 = 3 fd2 = 3 fd1 = 4 hello world1.1 fd只是一個(gè)數(shù)字,代表數(shù)字和文件之前的一個(gè)映射關(guān)系
查看/proc/14378/fd,可以看到映射關(guān)系
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是數(shù)字,/proc/14378/fd/4是一個(gè)文件,鏈接到/tmp/1.txt,/proc/14378/fd/4只存在內(nèi)存中,不存在硬盤中,程序中的數(shù)字4不是指向/proc/14378/fd/4文件
二、數(shù)字fd代表什么
2.1 task_struct
每一個(gè)進(jìn)程在內(nèi)核中的有一個(gè)task_struct的結(jié)構(gòu)體,結(jié)構(gòu)體中有一個(gè)file指針數(shù)組filp,fd代表filp這個(gè)數(shù)組的下標(biāo),fd = 4 就代表filp[4]指向的file結(jié)構(gòu)體
struct task_struct {...struct file * filp[NR_OPEN];... }2.2 file
struct file {unsigned short f_mode;//文件的類型和屬性u(píng)nsigned short f_flags;//文件打開的標(biāo)志unsigned short f_count;//關(guān)聯(lián)的fd的個(gè)數(shù)struct m_inode * f_inode;//file的真實(shí)實(shí)現(xiàn)off_t f_pos;//文件當(dāng)前的讀寫指針,讀到哪里了。 }2.3 file_table
內(nèi)核中還有一個(gè)全局的file_table,是file數(shù)組,保存所有file結(jié)構(gòu)體。
struct file file_table[NR_FILE];//系統(tǒng)級(jí)的一個(gè)file table2.4 關(guān)聯(lián)關(guān)系
兩個(gè)細(xì)節(jié)
dup:同進(jìn)程兩個(gè)fd指向同一個(gè)file
fork:兩個(gè)進(jìn)程的兩個(gè)fd指向同一個(gè)file
下面是新的Kernel關(guān)系圖
2.5 Binder傳輸fd
Binder傳輸fd,兩個(gè)進(jìn)程的不同fd指向了同一個(gè)file,享有相同的file offset和file status flag.
三、以pipe為例:一切皆文件
早期的錯(cuò)誤想法
硬件驅(qū)動(dòng)通過設(shè)備文件和用戶空間的應(yīng)用程序通信,是通過將驅(qū)動(dòng)的信息寫進(jìn)設(shè)備文件,然后應(yīng)用程序讀取設(shè)備文件的內(nèi)容。
3.1 pipe初始化
3.1.1 sys_pipe
系統(tǒng)調(diào)用pipe的實(shí)現(xiàn),會(huì)返回兩個(gè)fd給用戶空間
int sys_pipe(unsigned long * fildes)//系統(tǒng)調(diào)用生成一對(duì)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,并保存這兩個(gè)file結(jié)構(gòu)體if (j==1)//只找到一個(gè)f[0]->f_count=0;//將第一file重置,清空if (j<2)return -1;//沒找到一隊(duì),反正就是失敗j=0;for(i=0;j<2 && i<NR_OPEN;i++)//將兩個(gè)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 獲得一個(gè)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;//讓兩個(gè)file結(jié)構(gòu)體的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數(shù)值返回給用戶空間put_fs_long(fd[1],1+fildes);//將fd1數(shù)值返回給用戶空間return 0; }3.1.2 get_pipe_inode
創(chuàng)建一個(gè)m_inode結(jié)構(gòu)體
將m_inode的i_size指向一塊4096B的緩沖區(qū)
設(shè)置m_inode的i_pipe為1,標(biāo)識(shí)這個(gè)m_inode為pipe inode
3.1.3 get_free_page
申請(qǐng)一個(gè)物理頁,并返回內(nèi)核空間的地址
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
根據(jù)inode->i_pip,將sys_read變成read_pipe。
int sys_read(unsigned int fd,char * buf,int count)//文件讀的系統(tǒng)調(diào)用,fd->file->inode->數(shù)據(jù)塊 {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))//字符設(shè)備return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);if (S_ISBLK(inode->i_mode))//塊設(shè)備return block_read(inode->i_zone[0],&file->f_pos,buf,count);if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {//常規(guī)文件或目錄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
將緩沖區(qū)的數(shù)據(jù)讀到用戶空間char,讀count字節(jié)
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))) {//如果發(fā)現(xiàn)沒有內(nèi)容wake_up(&inode->i_wait);//喚醒寫端if (inode->i_count != 2) /* are there any writers? *///沒有寫端return read;sleep_on(&inode->i_wait);//沒有內(nèi)容就睡眠}chars = PAGE_SIZE-PIPE_TAIL(*inode);//判斷尾部的數(shù)據(jù)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
根據(jù)inode->i_pip,將sys_write變成write_pipe。
int sys_write(unsigned int fd,char * buf,int count)//文件寫的系統(tǒng)調(diào)用,fd->file->inode->數(shù)據(jù)塊 {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對(duì)應(yīng)的數(shù)據(jù)寫到緩沖區(qū),寫入count字節(jié)
int write_pipe(struct m_inode * inode, char * buf, int count)//寫pipe的實(shí)現(xiàn) {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);//計(jì)算管道頭部到緩沖區(qū)末端的空閑字節(jié)數(shù) 4098if (chars > count)chars = count;if (chars > size)chars = size;count -= chars;written += chars;size = PIPE_HEAD(*inode);//當(dāng)前的頭指正PIPE_HEAD(*inode) += chars;PIPE_HEAD(*inode) &= (PAGE_SIZE-1);while (chars-->0)((char *)inode->i_size)[size++]=get_fs_byte(buf++);//一個(gè)寫字符到管道}wake_up(&inode->i_wait);//寫完喚醒讀端return written; }3.4 pipe讀寫指針
其實(shí)pipe的讀寫指針并沒有保存在file結(jié)構(gòu)體,而是保存在m_inode。
如果緩沖區(qū)滿了,讀端一會(huì)不讀,會(huì)導(dǎo)致寫端的進(jìn)程sleep。
整個(gè)讀寫的過程,并沒有通過鎖來控制,而是通過ringbuffer來實(shí)現(xiàn),有興趣的可以自己研究。
3.5 思考一個(gè)問題
父進(jìn)程創(chuàng)建一對(duì)pipe的fd1 fd2
子進(jìn)程通過fork復(fù)制父進(jìn)程的pipe fd1 fd2
父進(jìn)程關(guān)閉讀的fd1
子進(jìn)程關(guān)閉寫的fd2
父子進(jìn)程就可以通過pipe進(jìn)行跨進(jìn)程通信
3.6 Linux的改進(jìn)
當(dāng)文件類型越來越多的時(shí)候,用file_operations結(jié)構(gòu)體代替大量if else
file_operations中保存read write的函數(shù)指針
我覺得這樣子理解更加合適:一切皆文件接口
四、普通文件
4.1重要數(shù)據(jù)結(jié)構(gòu)
4.1.1 m_inode
file中f_inode指向就是m_inode,也就是file對(duì)應(yīng)的真實(shí)實(shí)現(xiàn)。
i_dev:代表塊設(shè)備號(hào)
i_zone[9]:代表數(shù)據(jù)塊號(hào)
4.1.2 buffer_head
b_dev設(shè)備號(hào)
b_blocknr數(shù)據(jù)塊號(hào)
b_data指向一塊內(nèi)存
所有buffer_head會(huì)被存放在一個(gè)hash表中
可以調(diào)用下面兩個(gè)接口,完成數(shù)據(jù)塊的讀寫,這背后的實(shí)現(xiàn)就要看塊設(shè)備驅(qū)動(dòng)怎么實(shí)現(xiàn)的。
先記住,buffer_head(設(shè)備號(hào)+塊號(hào)+內(nèi)存地址)+讀寫指令 可以完成一次信息的交換。
ll_rw_block(READ,bh); ll_rw_block(WRITE,bh);4.2 讀文件
4.2.1 file_read
根據(jù)(filp->f_pos)/BLOCK_SIZE計(jì)算對(duì)應(yīng)的塊號(hào)nr
根據(jù)inode->i_dev和nr調(diào)用bread獲得buffer_head
調(diào)用ll_rw_block(READ,bh)請(qǐng)求數(shù)據(jù)塊的數(shù)據(jù)
將buffer_head中b_data拷貝到用戶空間的buf
4.2.2 bread
根據(jù)設(shè)備號(hào),數(shù)據(jù)塊號(hào),創(chuàng)建一個(gè)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,一般用這個(gè)就可以 {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);//將硬件的數(shù)據(jù)讀取到buffer_headwait_on_buffer(bh);if (bh->b_uptodate)return bh;brelse(bh);//釋放鎖return NULL; }4.3 寫文件
4.3.1 file_write
根據(jù)(filp->f_pos)/BLOCK_SIZE計(jì)算對(duì)應(yīng)的塊號(hào)block,如果文件不夠大,需要擴(kuò)容。
根據(jù)inode->i_dev和block調(diào)用bread獲得buffer_head
將用戶空間的buf拷貝到buffer_head中b_data。
4.3.2 sys_sync
ll_rw_block將buffer_head的臟數(shù)據(jù)同步到塊設(shè)備
int sys_sync(void)//系統(tǒng)調(diào)用,同步塊設(shè)備和內(nèi)存高速緩存中數(shù)據(jù) {int i;struct buffer_head * bh;sync_inodes(); /* write out inodes into buffers *///將修改的inode數(shù)據(jù)寫入到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);//真的寫到塊設(shè)備中}return 0; }五、塊設(shè)備
5.1 內(nèi)部結(jié)構(gòu)
5.2 mount("dev/block/sda0","/sdcard")
第一步:從dev/block/sda0中讀取第0塊inode塊上的數(shù)據(jù),讀取第一個(gè)d_inode的數(shù)據(jù),并構(gòu)建內(nèi)存中的m_inode。
第二步:將m_inode的i_num和"sdcard"按照dir_entry的結(jié)構(gòu)體存放在"/"目錄對(duì)應(yīng)m_inode指向的i_zone數(shù)據(jù)區(qū)域
5.3 int fd = open("/sdcard/1.txt")
第一步:找到m_inode("sdcard")
讀取"/"對(duì)應(yīng)的m_inode("/")的 i_zone[9]的數(shù)據(jù)到內(nèi)存中
根據(jù)"sdcard"得到inode號(hào),拿到"sdcard"對(duì)應(yīng)的m_inode("sdcard"),m_inode已經(jīng)在mount中創(chuàng)建。
第二步:創(chuàng)建m_inode("1.txt")
讀取m_inode("sdcard")的 i_zone[9]的數(shù)據(jù)到內(nèi)存中
根據(jù)"1.txt",拿到1.txt文件對(duì)應(yīng)的d_inode的號(hào)
計(jì)算d_inode號(hào)找到對(duì)應(yīng)的d_inode結(jié)構(gòu)體存放在塊設(shè)備的塊號(hào),塊號(hào)=inode/一個(gè)塊最多存放的d_inode結(jié)構(gòu)體
讀取的塊號(hào)對(duì)應(yīng)數(shù)據(jù)到內(nèi)存中,讀取對(duì)應(yīng)的d_inode數(shù)據(jù),構(gòu)建m_inode("sdcard")
第三步:將fd指向file指向m_inode
創(chuàng)建file指向m_inode("1.txt")
file[fd]指向file
返回fd
從此形成fd->file->m_inode的對(duì)應(yīng)關(guān)系,write read close 都可以對(duì)應(yīng)的轉(zhuǎn)化成file的操作,對(duì)應(yīng)的m_inode的操作
六、目前Linux的架構(gòu)
構(gòu)建了一個(gè)VFS層,虛擬文件系統(tǒng),各類文件系統(tǒng)可以更好的兼容,EXT4,F2FS
文件系統(tǒng)和塊設(shè)備的數(shù)據(jù)交互,用BIO代替了buffer_head
新增了Block Layer層對(duì)BIO進(jìn)行合并調(diào)度
? 回復(fù)「?籃球的大肚子」進(jìn)入技術(shù)群聊
回復(fù)「1024」獲取1000G學(xué)習(xí)資料
總結(jié)
以上是生活随笔為你收集整理的Linux Storage入门学习的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python 异步定时任务
- 下一篇: 你应该知道Linux内核softirq