Linux Storage入门学习

前言

本文大量代码基于linux 0.11,因为早期linux的版本更加适合初学者入门。虽然代码比较早,但是不妨碍我们学习Linux Storage的精髓。

一、hello world

1.1 Demo

 
  1. #include<stdio.h>

  2. #include<unistd.h>

  3. #include<sys/types.h>

  4. #include<sys/stat.h>

  5. #include<fcntl.h>

  6.  
  7. int main(int argc, char *argv[]) {

  8. printf("pid = %d\n", getpid());

  9. int fd1,fd2;

  10. char s[] = "hello world\n";

  11. //打开文件,拿到fd

  12. fd1 = open("/tmp/1.txt", O_RDWR | O_CREAT);

  13. printf("fd1 = %d\n", fd1);

  14. //写入

  15. write(fd1, s, sizeof(s));

  16. close(fd1);

  17. fd2 = open("/tmp/2.txt", O_RDWR | O_CREAT);

  18. printf("fd2 = %d\n", fd2);

  19. //打开文件,拿到fd

  20. fd1 = open("/tmp/1.txt", O_RDWR);

  21. printf("fd1 = %d\n", fd1);

  22. char buffer[80];

  23. //读取

  24. read(fd1, buffer, sizeof(buffer));

  25. //关闭fd

  26. printf("%s", buffer);

  27. getchar();//暂停程序

  28. close(fd1);

  29. close(fd2);

  30. return 0;

  31. }

运行结果

 
  1. pid = 14378

  2. fd1 = 3

  3. fd2 = 3

  4. fd1 = 4

  5. hello world

1.1 fd只是一个数字,代表数字和文件之前的一个映射关系

查看/proc/14378/fd,可以看到映射关系

 
  1. dr-x------ 2 wangbinhong tctnb 0 3月 14 16:27 .

  2. dr-xr-xr-x 9 wangbinhong tctnb 0 3月 14 16:26 ..

  3. lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 0 -> /dev/pts/19 //stdin

  4. lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 1 -> /dev/pts/19 //stdout

  5. lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 2 -> /dev/pts/19 //stderr

  6. lrwx------ 1 wangbinhong tctnb 64 3月 14 16:27 3 -> /tmp/2.txt

  7. 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结构体

 
  1. struct task_struct {

  2. ...

  3. struct file * filp[NR_OPEN];

  4. ...

  5. }

2.2 file

 
  1. struct file {

  2. unsigned short f_mode;//文件的类型和属性

  3. unsigned short f_flags;//文件打开的标志

  4. unsigned short f_count;//关联的fd的个数

  5. struct m_inode * f_inode;//file的真实实现

  6. off_t f_pos;//文件当前的读写指针,读到哪里了。

  7. }

2.3 file_table

内核中还有一个全局的file_table,是file数组,保存所有file结构体。

struct file file_table[NR_FILE];//系统级的一个file table

2.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给用户空间

 
  1. int sys_pipe(unsigned long * fildes)//系统调用生成一对pipe

  2. {

  3. struct m_inode * inode;

  4. struct file * f[2];

  5. int fd[2];

  6. int i,j;

  7.  
  8. j=0;

  9. for(i=0;j<2 && i<NR_FILE;i++)

  10. if (!file_table[i].f_count)//找到空闲的file

  11. (f[j++]=i+file_table)->f_count++;//将空闲的file的f_count+1,并保存这两个file结构体

  12. if (j==1)//只找到一个

  13. f[0]->f_count=0;//将第一file重置,清空

  14. if (j<2)

  15. return -1;//没找到一队,反正就是失败

  16. j=0;

  17. for(i=0;j<2 && i<NR_OPEN;i++)//将两个file的指针分别保存到current->filp[i]的空闲处

  18. if (!current->filp[i]) {

  19. current->filp[ fd[j]=i ] = f[j];

  20. j++;

  21. }

  22. if (j==1)

  23. current->filp[fd[0]]=NULL;

  24. if (j<2) {

  25. f[0]->f_count=f[1]->f_count=0;

  26. return -1;

  27. }//和上面逻辑类似

  28. if (!(inode=get_pipe_inode())) {//详见3.1.2 获得一个pipe inode

  29. current->filp[fd[0]] =

  30. current->filp[fd[1]] = NULL;

  31. f[0]->f_count = f[1]->f_count = 0;

  32. return -1;

  33. }

  34. f[0]->f_inode = f[1]->f_inode = inode;//让两个file结构体的f_inode指向pipe inode

  35. f[0]->f_pos = f[1]->f_pos = 0;//重置读写指针

  36. f[0]->f_mode = 1; /* read */

  37. f[1]->f_mode = 2; /* write */

  38. put_fs_long(fd[0],0+fildes);//将fd0数值返回给用户空间

  39. put_fs_long(fd[1],1+fildes);//将fd1数值返回给用户空间

  40. return 0;

  41. }

3.1.2 get_pipe_inode

创建一个m_inode结构体
将m_inode的i_size指向一块4096B的缓冲区
设置m_inode的i_pipe为1,标识这个m_inode为pipe inode

 
  1. struct m_inode * get_pipe_inode(void)//返回一个空的pipe inode用于pipe

  2. {

  3. struct m_inode * inode;

  4.  
  5. if (!(inode = get_empty_inode()))

  6. return NULL;

  7. if (!(inode->i_size=get_free_page())) {//申请一个物理页4096B作为环形管道缓冲区,缓冲区指针保存到i_size。

  8. inode->i_count = 0;

  9. return NULL;

  10. }

  11. inode->i_count = 2; /* sum of readers/writers */

  12. PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;

  13. inode->i_pipe = 1;//表示为pipe的m_inode

  14. return inode;

  15. }

3.1.3 get_free_page

申请一个物理页,并返回内核空间的地址

 
  1. unsigned long get_free_page(void)

  2. {

  3. register unsigned long __res asm("ax");

  4.  
  5. __asm__("std ; repne ; scasb\n\t"

  6. "jne 1f\n\t"

  7. "movb $1,1(%%edi)\n\t"

  8. "sall $12,%%ecx\n\t"

  9. "addl %2,%%ecx\n\t"

  10. "movl %%ecx,%%edx\n\t"

  11. "movl $1024,%%ecx\n\t"

  12. "leal 4092(%%edx),%%edi\n\t"

  13. "rep ; stosl\n\t"

  14. "movl %%edx,%%eax\n"

  15. "1:"

  16. :"=a" (__res)

  17. :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),

  18. "D" (mem_map+PAGING_PAGES-1)

  19. :"di","cx","dx");

  20. return __res;

  21. }

3.2 读pipe

3.2.1 sys_read

根据inode->i_pip,将sys_read变成read_pipe。

 
  1. int sys_read(unsigned int fd,char * buf,int count)//文件读的系统调用,fd->file->inode->数据块

  2. {

  3. struct file * file;

  4. struct m_inode * inode;

  5.  
  6. if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))

  7. return -EINVAL;

  8. if (!count)

  9. return 0;

  10. verify_area(buf,count);

  11. inode = file->f_inode;

  12. if (inode->i_pipe)//pipe

  13. return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;//3.2.2

  14. if (S_ISCHR(inode->i_mode))//字符设备

  15. return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);

  16. if (S_ISBLK(inode->i_mode))//块设备

  17. return block_read(inode->i_zone[0],&file->f_pos,buf,count);

  18. if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {//常规文件或目录

  19. if (count+file->f_pos > inode->i_size)

  20. count = inode->i_size - file->f_pos;

  21. if (count<=0)

  22. return 0;

  23. return file_read(inode,file,buf,count);

  24. }

  25. printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);

  26. return -EINVAL;

  27. }

3.2.2 read_pipe

将缓冲区的数据读到用户空间char,读count字节

 
  1. int read_pipe(struct m_inode * inode, char * buf, int count)//读取pipe

  2. {

  3. int chars, size, read = 0;

  4.  
  5. while (count>0) {

  6. while (!(size=PIPE_SIZE(*inode))) {//如果发现没有内容

  7. wake_up(&inode->i_wait);//唤醒写端

  8. if (inode->i_count != 2) /* are there any writers? *///没有写端

  9. return read;

  10. sleep_on(&inode->i_wait);//没有内容就睡眠

  11. }

  12. chars = PAGE_SIZE-PIPE_TAIL(*inode);//判断尾部的数据

  13. if (chars > count)

  14. chars = count;

  15. if (chars > size)

  16. chars = size;

  17. count -= chars;

  18. read += chars;

  19. size = PIPE_TAIL(*inode);//头部开始读的指针

  20. PIPE_TAIL(*inode) += chars;

  21. PIPE_TAIL(*inode) &= (PAGE_SIZE-1);

  22. while (chars-->0)

  23. put_fs_byte(((char *)inode->i_size)[size++],buf++);

  24. }

  25. wake_up(&inode->i_wait);

  26. return read;

  27. }

3.3 写pipe

3.3.1 sys_write

根据inode->i_pip,将sys_write变成write_pipe。

 
  1. int sys_write(unsigned int fd,char * buf,int count)//文件写的系统调用,fd->file->inode->数据块

  2. {

  3. struct file * file;

  4. struct m_inode * inode;

  5.  
  6. if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))

  7. return -EINVAL;

  8. if (!count)

  9. return 0;

  10. inode=file->f_inode;

  11. if (inode->i_pipe)

  12. return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;//3.3.2

  13. if (S_ISCHR(inode->i_mode))

  14. return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos);

  15. if (S_ISBLK(inode->i_mode))

  16. return block_write(inode->i_zone[0],&file->f_pos,buf,count);

  17. if (S_ISREG(inode->i_mode))

  18. return file_write(inode,file,buf,count);

  19. printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);

  20. return -EINVAL;

  21. }

3.3.2 write_pipe

将用户空间char对应的数据写到缓冲区,写入count字节

 
  1. int write_pipe(struct m_inode * inode, char * buf, int count)//写pipe的实现

  2. {

  3. int chars, size, written = 0;

  4.  
  5. while (count>0) {

  6. while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//如果写满了,size为0

  7. wake_up(&inode->i_wait);//唤醒读端

  8. if (inode->i_count != 2) { /* no readers *///没有读者直接返回

  9. current->signal |= (1<<(SIGPIPE-1));

  10. return written?written:-1;

  11. }

  12. sleep_on(&inode->i_wait);//写端休眠

  13. }

  14. chars = PAGE_SIZE-PIPE_HEAD(*inode);//计算管道头部到缓冲区末端的空闲字节数 4098

  15. if (chars > count)

  16. chars = count;

  17. if (chars > size)

  18. chars = size;

  19. count -= chars;

  20. written += chars;

  21. size = PIPE_HEAD(*inode);//当前的头指正

  22. PIPE_HEAD(*inode) += chars;

  23. PIPE_HEAD(*inode) &= (PAGE_SIZE-1);

  24. while (chars-->0)

  25. ((char *)inode->i_size)[size++]=get_fs_byte(buf++);//一个写字符到管道

  26. }

  27. wake_up(&inode->i_wait);//写完唤醒读端

  28. return written;

  29. }

3.4 pipe读写指针

其实pipe的读写指针并没有保存在file结构体,而是保存在m_inode。
如果缓冲区满了,读端一会不读,会导致写端的进程sleep。
整个读写的过程,并没有通过锁来控制,而是通过ringbuffer来实现,有兴趣的可以自己研究。

 
  1. struct m_inode {

  2. ...

  3. unsigned long i_size;//被作为指针指向申请的缓冲区,一个缓冲区4096B

  4. unsigned short i_zone[9];//用i_zone[0]代表写的游标,用i_zone[1]代表写的游标

  5. ...

  6. }

3.5 思考一个问题

父进程创建一对pipe的fd1 fd2
子进程通过fork复制父进程的pipe fd1 fd2
父进程关闭读的fd1
子进程关闭写的fd2
父子进程就可以通过pipe进行跨进程通信

3.6 Linux的改进

当文件类型越来越多的时候,用file_operations结构体代替大量if else
file_operations中保存read write的函数指针

 
  1. struct file {

  2. const struct file_operations *f_op;

  3. void *private_data;//这个很重要

  4. }

  5.  
  6. struct file_operations {

  7. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

  8. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

  9. };

我觉得这样子理解更加合适:一切皆文件接口

四、普通文件

4.1重要数据结构

4.1.1 m_inode

file中f_inode指向就是m_inode,也就是file对应的真实实现。
i_dev:代表块设备号
i_zone[9]:代表数据块号

 
  1. struct m_inode {

  2. unsigned short i_mode;//15-12文件类型,11-9保存执行文件设置,8-0保存文件权限

  3. unsigned short i_uid;//文件宿主的用户id

  4. unsigned long i_size;//文件长度

  5. unsigned long i_mtime;//修改时间

  6. unsigned char i_gid;//文件宿主的组id

  7. unsigned char i_nlinks;//硬链接的次数

  8. unsigned short i_zone[9];//对应数据块 0-6直接是块号,7一次间接块,8二次间接块,一个块是1KB=1024Byte

  9. //因为块号用short来表示,也就是2Byte,所以一个块可以存放512个块号,所以一次块512个,二次块就是512*512。

  10. //所以变相的可以算出一个文件的最大size是7+512+512*512 kb

  11. //一般逻辑块的大小会和buffer_head大小一样。

  12. /* these are in memory also */

  13. struct task_struct * i_wait;

  14. unsigned long i_atime;

  15. unsigned long i_ctime;

  16. unsigned short i_dev;//设备号

  17. unsigned short i_num;

  18. unsigned short i_count;

  19. unsigned char i_lock;

  20. unsigned char i_dirt;

  21. unsigned char i_pipe;

  22. unsigned char i_mount;

  23. unsigned char i_seek;

  24. unsigned char i_update;

  25. };

4.1.2 buffer_head

b_dev设备号
b_blocknr数据块号
b_data指向一块内存
所有buffer_head会被存放在一个hash表中

 
  1. struct buffer_head {

  2. char * b_data; /* pointer to data block (1024 bytes) */

  3. unsigned long b_blocknr; /* block number */ //块号

  4. unsigned short b_dev; /* device (0 = free) */ //设备号

  5. unsigned char b_uptodate;

  6. unsigned char b_dirt; /* 0-clean,1-dirty */

  7. unsigned char b_count; /* users using this block */

  8. unsigned char b_lock; /* 0 - ok, 1 -locked */

  9. struct task_struct * b_wait;

  10. struct buffer_head * b_prev;

  11. struct buffer_head * b_next;

  12. struct buffer_head * b_prev_free;

  13. struct buffer_head * b_next_free;

  14. };

可以调用下面两个接口,完成数据块的读写,这背后的实现就要看块设备驱动怎么实现的。

先记住,buffer_head(设备号+块号+内存地址)+读写指令 可以完成一次信息的交换。

 
  1. ll_rw_block(READ,bh);

  2. 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

 
  1. int file_read(struct m_inode * inode, struct file * filp, char * buf, int count)

  2. {

  3. int left,chars,nr;

  4. struct buffer_head * bh;

  5.  
  6. if ((left=count)<=0)

  7. return 0;

  8. while (left) {

  9. if (nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE)) {

  10. if (!(bh=bread(inode->i_dev,nr)))//4.2.2

  11. break;

  12. } else

  13. bh = NULL;

  14. nr = filp->f_pos % BLOCK_SIZE;

  15. chars = MIN( BLOCK_SIZE-nr , left );

  16. filp->f_pos += chars;

  17. left -= chars;

  18. if (bh) {

  19. char * p = nr + bh->b_data;

  20. while (chars-->0)

  21. put_fs_byte(*(p++),buf++);

  22. brelse(bh);

  23. } else {

  24. while (chars-->0)

  25. put_fs_byte(0,buf++);

  26. }

  27. }

  28. inode->i_atime = CURRENT_TIME;

  29. return (count-left)?(count-left):-ERROR;

  30. }

4.2.2 bread

根据设备号,数据块号,创建一个buffer_head

 
  1. /*

  2. * bread() reads a specified block and returns the buffer that contains

  3. * it. It returns NULL if the block was unreadable.

  4. */

  5. struct buffer_head * bread(int dev,int block)//从dev,block获得buffer_head,一般用这个就可以

  6. {

  7. struct buffer_head * bh;

  8.  
  9. if (!(bh=getblk(dev,block)))//拿一块空闲的buffer_head

  10. panic("bread: getblk returned NULL\n");

  11. if (bh->b_uptodate)

  12. return bh;

  13. ll_rw_block(READ,bh);//将硬件的数据读取到buffer_head

  14. wait_on_buffer(bh);

  15. if (bh->b_uptodate)

  16. return bh;

  17. brelse(bh);//释放锁

  18. return NULL;

  19. }

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。

 
  1. int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)

  2. {

  3. off_t pos;

  4. int block,c;

  5. struct buffer_head * bh;

  6. char * p;

  7. int i=0;

  8.  
  9. /*

  10. * ok, append may not work when many processes are writing at the same time

  11. * but so what. That way leads to madness anyway.

  12. */

  13. if (filp->f_flags & O_APPEND)

  14. pos = inode->i_size;

  15. else

  16. pos = filp->f_pos;

  17. while (i<count) {

  18. if (!(block = create_block(inode,pos/BLOCK_SIZE)))//如果写文件的时候发现文件不够大,就要扩容

  19. break;

  20. if (!(bh=bread(inode->i_dev,block)))

  21. break;

  22. c = pos % BLOCK_SIZE;

  23. p = c + bh->b_data;

  24. bh->b_dirt = 1;

  25. c = BLOCK_SIZE-c;

  26. if (c > count-i) c = count-i;

  27. pos += c;

  28. if (pos > inode->i_size) {

  29. inode->i_size = pos;

  30. inode->i_dirt = 1;

  31. }

  32. i += c;

  33. while (c-->0)

  34. *(p++) = get_fs_byte(buf++);

  35. brelse(bh);

  36. }

  37. inode->i_mtime = CURRENT_TIME;

  38. if (!(filp->f_flags & O_APPEND)) {

  39. filp->f_pos = pos;

  40. inode->i_ctime = CURRENT_TIME;

  41. }

  42. return (i?i:-1);

  43. }

4.3.2 sys_sync

ll_rw_block将buffer_head的脏数据同步到块设备

 
  1. int sys_sync(void)//系统调用,同步块设备和内存高速缓存中数据

  2. {

  3. int i;

  4. struct buffer_head * bh;

  5.  
  6. sync_inodes(); /* write out inodes into buffers *///将修改的inode数据写入到buffer_head.

  7. bh = start_buffer;

  8. for (i=0 ; i<NR_BUFFERS ; i++,bh++) {

  9. wait_on_buffer(bh);

  10. if (bh->b_dirt)

  11. ll_rw_block(WRITE,bh);//真的写到块设备中

  12. }

  13. return 0;

  14. }

五、块设备

5.1 内部结构

 
  1. struct d_inode { //块设备中对应的inode的结构体

  2. unsigned short i_mode;

  3. unsigned short i_uid;

  4. unsigned long i_size;

  5. unsigned long i_time;

  6. unsigned char i_gid;

  7. unsigned char i_nlinks;

  8. unsigned short i_zone[9];

  9. };

 
  1. struct dir_entry { //目录

  2. unsigned short inode;//inode

  3. char name[NAME_LEN];//inode对应的文件名

  4. };

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进行合并调度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值