linux源码通俗解读

VFS部分是0.95的代码,内存部分是2.6.11领略四级页表和虚存,其余是0.11

文件系统在磁盘中的体现

下面是磁盘的内容,其中i节点就是一个inode数组,逻辑块就是数据块可用于存放数据
linux源码通俗解读_第1张图片

操作系统通过将磁盘数据读入到内存中指定的缓冲区块来与磁盘交互,对内存中的缓冲区块修改后写回磁盘。

进程(task_struct * task[NR_TASKS] = {&(init_task.task), }; )、
系统打开文件表(file file_table[NR_FILE])、
超级块、
inode
等等在linux中都有唯一且有限的全局数组,比如创建新进程或者打开新的文件时就需要在这个数组中找到一个空位(槽)填写相应内容否则不允许进行,因为这些都是系统资源,你可以理解为os只能管理有限的资源

bread(int dev,int block)函数返回一个缓冲区块的头部(用于解释缓冲区,其内部有指针指向具体缓冲区块的地址)的地址,作用是从设备号为dev的设备中读取第block块数据块,缓冲区块和文件系统的了逻辑块大小一样!

若干个扇区作为一个逻辑块(linux0.11中1个逻辑块是两个扇区即1MB),若干个逻辑块作为一个簇,因为随着磁盘容量增大,如果分配空间的单位不增大会导致数据块位图增大从而又浪费了磁盘空间。其中,逻辑块是逻辑上的,也就是通过软件实现的,具体到读写底层(即利用汇编提供的读写磁盘中断)时,仍然是以扇区为单位读写

缓冲区

准确地说下图的缓冲区之间通过buffer_head的b_next_free、b_prev_free指针连接而成,在系统初始化的buffer_init中完成这些连接
linux源码通俗解读_第2张图片linux源码通俗解读_第3张图片除此以外,还有一个哈希链表数组hash_table(一开始空,当某个缓冲区被用到了才会加入到该数组),根据设备号和块号哈希映射到数组的某个索引,冲突则通过链表连接起来(buffer_head的b_prev和b_next)linux源码通俗解读_第4张图片
暂时发现只有在读写缓冲区的时候缓冲区才会被上锁,

uptodate针对读,为1的时候表示缓冲区内容和磁盘中的一致;dirt针对写,为1的时候表示缓冲区被更新了,需要同步到磁盘

inode的i_count和file的f_count区别

inode的i_count:
file的f_count:

磁盘上的inode表并不会被加载到OS

只有当具体某个文件被读或写时,其inode才会被加载到内存的inode缓存表中,即inode_table[NR_INODE],每个inode元素都被初始化为0了,相当于提前先生成inode对象,使得内存中常驻NR_INODE个inode可被使用,而不必临时new一个,只需要直接初始化空闲inode的每个属性,这会快很多

linux0.11需要手动将脏缓冲区同步到磁盘上

除了少数条件会自动触发自动同步之外,码农需要手动调用sys_sync系统调用使得刚刚写的文件会立刻同步到磁盘上,否则你得等。linux0.11做的仅仅是标记该缓冲区为脏。在2.6版本,linux会专门有个pdflush线程周期性地检查是否有脏缓冲区并且自动同步到磁盘

linux0.11下设备文件的inode->i_zone[0]是设备号,而0.11后用inode->rdev代表设备号

多个空闲缓冲区是资源,共用一个等待队列,当没有空闲缓冲区时,申请空闲缓冲区的进程加入到该等待队列并阻塞,但每个缓冲区自身还有一个等待队列用于互斥,比如这时候突然有一个空闲缓冲区了,那么申请空闲缓冲区的队列中的所有进程都被唤醒,全部(n)去争夺这个新的空闲缓冲区,于是(n-1)都加入到该缓冲区的等待队列

这样设计是可理解的,没有空闲缓冲区的时候,申请该资源的进程没有可以加入的队列(不知道加入到哪个缓冲区),现在专门用一个队列来让他们排队,同时也能阻塞他们了

linux下的五种进程状态

#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 3
#define TASK_STOPPED 4

1.TASK_RUNNING:可运行状态,处于该状态的进程可以被调度执行而成为当前进程.
2.TASK_INTERRUPTIBLE:可中断睡眠状态,处于该状态的进程在所需资源有效时被唤醒,也可以通过信号或者定时中断唤醒.
3.TASK_UNINTERRUPTIBLE:不可中断睡眠状态,处于该状态的进程仅当所需资源有效时被唤醒.
4.TASK_ZOMBLE:僵尸状态,表示进程结束且释放资源.但其task_struct仍未释放.
5.TASK_STOPPED:暂停状态.处于该状态的进程通过其他进程的信号才能被唤醒

c语言进程虚拟地址空间

linux源码通俗解读_第5张图片

缺页中断原因

  1. 导致缺页异常的虚拟地址根本不在进程的“虚存区间”中,段错误。(栈扩展是一种例外情况)

  2. 地址在“虚存区间”中,但“虚存区间”的访问权限不够;例如“区间”是只读的,而你想写,段错误

  3. 映射关系没建立

  4. 映射关系也建立了,但是页面不在内存中。肯定是换出到交换分区中了,换进来再说

  5. 页面也在内存中。但页面的访问权限不够。例如页面是只读的,而你想写。这通常就是 “写时拷贝COW” 的情况。

  6. 缺页异常发生在“内核动态映射空间”。这是由于进程进入内核后,访问一个通过 vmalloc() 获得线性地址而引起的异常。对这种情况,需要将内核页目录表、页表中对应的映射关系拷贝到进程的页目录表和页表中。

sched.c

sleep_on(struct task_struct **p)

作用:将当前执行该函数的进程即CURRENT插入到等待队列的队首并阻塞,其中p是等待某个资源的队列的队首的pcb的指针的指针

实现:

  1. 调用__sleep_on(p,TASK_UNINTERRUPTIBLE);

__sleep_on(struct task_struct **p, int state)

作用:将当前进程插入到等待进程队列的队首指针p(头插),并将队首指针指向当前进程

实现:

  1. 如果进程0尝试阻塞即if (current == &(init_task.task))则直接报错
  2. 将当前进程CURRENT插入到等待队列(千万注意!这里的队列其实是栈,只是我们很少说等待栈,反正就是后到的进程先出)的队首p即tmp = *p; *p = current; current->state = state; 这三行代码隐含了一个等待队列(而不是显式),实现十分巧妙,因为当前进程CURRENT执行__sleep_on这种内核函数会专门有自己的内核栈来保存临时变量,因此tmp被保存在CURRENT的内核栈中,于是CURRENT通过tmp能够找到等待队列中的前一个等待进程,此时队首指针p指向当前进程(其中p永远指向队首进程),然后将当前进程状态设置为阻塞态
  3. 打开中断即汇编sti指令
  4. 调度其他进程运行即schedule(),这时同样需要该资源的新进程就可以开始执行__sleep_on函数(通常,参考),经历多轮时钟中断和调度后,阻塞事件完成或资源空闲了(会主动调用释放解锁了,比如读写块到缓冲区函数ll_rw_block底层在一开始上锁,当且仅当读写完成才解锁),当等待队列的队首进程被唤醒即wake_up后(唤醒前也是卡在schedule()),继续向下执行,令队首进程的下一个等待进程作为队首即*p = tmp,并且将新的队首进程唤醒即tmp->state=0,依次进行下去,即旧队首唤醒新队首,最后整个等待队列都被唤醒重新一起争夺资源(资源一旦空闲,所有等待进程被唤醒)

wake_up(struct task_struct **p)

作用:将传入的进程p唤醒

实现:

  1. 将传入的进程p的状态修改为就绪态即(**p).state=0;

sys_pause()

返回值:int

作用:

实现:

  1. 将当前进程设为可中断的阻塞态即current->state = TASK_INTERRUPTIBLE,意味着当前进程恢复(收到信号)之前不会给调度(对于进程0而言是意外即不受阻塞态影响,依然可以被调度(相当于只有schedule()起作用),因为当没有进程需要运行时,进程0会被调度,于是被称为idle进程,一直执行for(; pause(),相当于不断地找进程来运行尽管可能没有)
  2. 调度其他程序即schedule()

super.c

超级块全局数组super_block super_block[NR_SUPER]
linux源码通俗解读_第6张图片设备号为0==空闲超级块槽

get_super(int dev)

返回值: super_block *

作用:从超级块全局数组中获取设备号对应设备的超级块

实现:遍历超级块全局数组,直到当前被遍历超级块的设备号与dev相等

put_super(int dev)

作用:释放(即清空、初始化)超级块全局数组中设备号所对应设备的超级块

实现:

  1. 对该超级块上锁
  2. 该超级块的设备号设置为0(作为空闲超级块槽的依据)
  3. 释放(brelse)i节点位图和逻辑块位图所占用的缓冲区块
  4. 解锁该超级块,并唤醒等待超级块全局数组空槽的进程

read_super(int dev)

返回值:super_block *

作用:找到超级块全局数组空槽并从该设备读取超级块到空槽中

实现:

  1. 遍历超级块全局数组(缓存)查找是否已经有此超级块,有则直接返回
  2. 遍历超级块全局数组找到dev==0的空槽
  3. 读取即bh = bread(dev,1)超级块并对超级块上锁
  4. 初始化空槽
  5. 释放该缓冲区块
  6. 根据刚才读取的超级块确定i节点位图和逻辑块位图
  7. 分别读取两个位图到超级块结构体中的s_imap数组和s_zmap数组(每次读一块并且将缓冲头地址赋给数组当前元素)
  8. 将i节点位图和逻辑块位图中第一个数据块设置为已被占用(不许用,为了后面方便)
  9. 解锁超级块

sys_umount(char * dev_name)

返回值:int

作用:根据设备名(即dev_name,准确说是全路径名)卸载指定设备。注意!对于设备文件,其inode的i_zone[0]是设备号

实现:

  1. 根据dev_name全路径名获取(namei)到该设备的inode
  2. 判断如果不是块设备则释放(iput)设备inode并返回错误
  3. 释放设备inode
  4. 判断如果设备号是根设备的则返回错误
  5. 判断如果读取超级块(get_super)失败或者设备未挂载(super_block->s_imount==0)则返回错误
  6. 判断如果挂载的节点的挂载数为0(super_block->s_imount->i_mount==0)则返回错误(你说你挂载在某个inode,但是这个inode根本就没有表明自己被挂载了)

namei(char * pathname)

返回值:inode *

作用:根据全路径名获取到该文件的inode

实现:

set_bit(bitnr,addr)

返回值:register int

作用:返回起始于addr内存段中的第bitnr位的值

实现:

register int __res __asm__("ax"); 
__asm__("bt %2,%3;setb %%al":
"=a" (__res):"a" (0),
"r" (bitnr),"m" (*(addr))); 
__res;


内联汇编的输入。
"a"(0), eax = 0;
"r"(bitnr), 任意空闲寄存器(假设为ebx), ebx=bitnr;
"m"(*(addr)), 内存变量*(addr);
内联汇编语句。
bt %2, %3 -> bt ebx, *addr, 
检测*addr的ebx位是否为1,1则eflag.CF=1, 否则eflag.CF=0;
setb %%al, al=CF;
内联汇编输出。
__res = eax。
__res作为set_bit(bitnr, addr)宏代表表达式的最终值。*/

mount_root()

作用:开始加载文件系统

实现:

  1. 初始化全局文件打开表的每个元素的引用数为0(file_table[i].f_count=0,文件每被open一次f_count加1)
  2. 初始化全局超级块数组
  3. 读取根设备的超级块(p=read_super(ROOT_DEV))
  4. 从根设备上读取第ROOT_INO个inode(mi=iget(ROOT_DEV,ROOT_INO),其中ROOT_INO==1即根inode)
  5. ????(mi->i_count += 3)
  6. p->s_isup = p->s_imount = mi(分别是当前文件系统的根inode以及挂载点的inode,比如现在有根文件系统,插入u盘后需要挂载到根文件系统中,因此此时u盘的超级块中的s_isup是u盘的根inode,s_imount是位于根文件系统的挂载点的inode)
  7. 将当前进程的当前工作目录和根目录均设置为根inode(current->pwd = mi;current->root = mi)
  8. 利用位图统计空闲inode数和空闲数据块数
	free=0;
	i=p->s_nzones;
	/*p->s_nzones是当前文件系统的总数据块数
	即图中的蓝色部分。 
	i是int类型,16位*/               
	while (-- i >= 0)
		if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))
			free++;
	/*i是当前数据块的序号,8191的二进制是连续13个1,即i&8191取低13位即求出当前数据块在缓冲区块中的偏移,i>>13即求出当前数据块属于第几个缓冲区块,i>>13的值在0~8之间,因为i是16位*/
	printk("%d/%d free blocks\n\r",free,p->s_nzones);
	free=0;
	i=p->s_ninodes+1;
	while (-- i >= 0)
		if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
			free++;
	printk("%d/%d free inodes\n\r",free,p->s_ninodes);

调用链:init.c->sys_setup->mount_root

inode.c

iget(int dev,int nr)

返回值:inode *

作用:从设备号为dev的设备上读取第nr个inode

实现:

_bmap(struct inode * inode,int block,int create)

返回值:int

作用:根据inode的i_data得到该文件逻辑块号为block的全局物理块号。create=1时,如果逻辑块block还未被分配全局物理块号,则按照分配算法分配给该逻辑块,将该逻辑块映射到物理块。create=0时即使未被分配也不管,直接返回初始值。
linux源码通俗解读_第7张图片

实现:

  1. 若block在直接寻址的范围内,则直接返回inode->i_data[block]
  2. 若block在一次寻址的范围内,则先读取一次间址块bh=bread(inode->i_dev,inode->i_data[7]),获取对应全局物理块号i = ((unsigned short *) (bh->b_data))[block];,然后释放缓冲区,最后返回块号i
  3. 同理若block在二次寻址的范围内,则需要读两次间址块最后返回块号
  4. 如果create=1且以上过程中发现逻辑块block没有对应的全局物理块号即初始值0(或-1???)则调用minix_new_block(int dev)通过分配算法分配块,然后inode设置为脏

get_empty_inode()

返回值:inode *

作用:从inode缓存表中找到空闲的inode节点并初始化

实现:

  1. 遍历inode_table,如果当前inode引用计数i_count==0,则符合最低条件(还不是最优),然后继续循环,只有当前inode引用计数i_count==0并且该文件未被修改过即i_dirt以及未被上锁即i_lock,则立即跳出循环,该inode为最优
  2. 如果找到的空闲inode符合最低条件,但该inode被修改过,则需要先把inode写回磁盘(write_inode(inode)->minix_write_inode(inode)),然后该inode才可以被使用
  3. 初始化得到的空闲inode即memset(inode,0,sizeof(*inode));
  4. 将inode引用计数设置为1即inode->i_count = 1;

namei.c—/fs/minix/namei.c

minix_mknod(struct inode * dir, const char * name, int len, int mode, int rdev)

返回值:int

作用:在minix文件系统下创建设备文件。

实现:

  1. 根据basename在dir中检查该设备文件是否已经存在bh = minix_find_entry(&dir,basename,namelen,&de),已存在则直接返回 文件已存在错误
  2. 在dir所在设备中找到一个空闲的inode即inode = minix_new_inode(dir->i_dev);,并将inode的idev初始化为所在目录inode的设备号即inode->i_dev = dev;
  3. 根据传入的mode初始化该inode即inode->i_mode = mode;
  4. 根据mode判断如果是设备文件(块设备文件或者字符设备文件)则用rdev初始化inode所代表的设备的设备号即inode->i_rdev = rdev;
  5. 将该inode设为脏即inode->i_dirt = 1;
  6. 在dir的目录文件中找到空闲目录项并将其目录项名设置name即bh = minix_add_entry(dir,name,len,&de),这里的传入的de被设置为空闲目录项的地址
  7. 将空闲目录项的inode编号设置为在2所找到的空闲inode即de->inode = inode->i_ino,并将该目录项所在缓冲区设脏即bh->b_dirt = 1;

minix_add_entry(struct inode * dir,const char * name, int namelen, struct minix_dir_entry ** res_dir)

返回值:buffer_head *

作用:在minix文件系统下,只把name加入目录文件dir的目录即minix_dir_entry数组,inode统一被初始化为0

实现:

  1. 先获取首个(直接索引)逻辑块的物理块号block = dir->i_data[0]
  2. 读入首个逻辑块即bh = bread(dir->i_dev,block)
  3. 开始遍历目录文件dir的目录(读入目录文件的内容即minix_dir_entry数组到缓冲区),直到de->inode==0即目录项空闲(如果遍历到最后一个目录项仍不空闲,则选择最后一个目录项的下一个目录项(且修改dir的文件大小和设脏,因为新增了目录项),并且如果目录项的数目刚好占用一个块,则申请一个新的物理块并建立好逻辑块与物理块的映射即block = minix_create_block(dir,i/DIR_ENTRIES_PER_BLOCK),然后读入该新物理块即
    bh = bread(dir->i_dev,block)),则把name填入到de->name[]中且缓冲区设脏,minix文件系统对文件名长度进行了限制即MINIX_NAME_LEN=14,如果name的长度超出则截断,没超出则填充0即
    for (i=0; i < MINIX_NAME_LEN ; i++)
    de->name[i]=(i
  4. 将新目录项de(是地址)赋值给res_dir即*res_dir = de;
  5. 返回空闲目录项所在缓冲区的头部bh

dir_namei(const char * pathname,int * namelen, const char ** name)

返回值:inode *

作用:获取pathname中最底层目录的inode,并且用户传入的name会被赋值为最底层的目录名或者文件名(如/a/b/c得到c)

实现:

  1. 调用get_dir()获取目录pathname的inode
  2. 循环遍历pathname,每遇到“/”就令name指向/的下一个字符的地址,最后会得到最底层的目录名(如/a/b/c得到c)

get_dir(const char * pathname)

返回值:inode *

作用:获取目录pathname最底层目录的inode。比如/var/log/httpd,将只返回 log/目录的inode,/var/log/httpd/则返回httpd/的目录

实现:

  1. 判断pathname第一个字符是否为/,是则代表pathname为绝对路径,令临时变量
    inode = current->root,pathname++。否则
    inode = current->pwd;
  2. 目录引用数加1即inode->i_count++;
  3. 依次遍历pathname中的每一个目录,即依次获得两个/之间夹住的目录名,根据该名字thisname和当前父目录的inode有
    bh = find_entry(&inode,thisname,namelen,&de)找到该子目录项de即dir_entry类型
    释放高速缓冲区bh即brelse()以及inode节点iput()
    根据de获得该子目录inode的编号即
    inr = de->inode,又根据当前父目录获得设备号idev = inode->i_dev,于是得到该子目录inode即inode = iget(idev,inr),回到3

minix_find_entry(struct inode * dir,const char * name, int namelen, struct minix_dir_entry ** res_dir)

返回值:buffer_head *

作用:根据目录dir的inode找到其下名为name的目录项。其中res_dir存放该目录项,而返回值是该目录项所在高速缓冲区的头部。

实现:

  1. 根据entries =
    (*dir)->i_size / (sizeof (struct minix_dir_entry));得到该目录下目录项的数目
  2. 先得到第一个逻辑块的对应物理块号
    block = (*dir)->i_zone[0]
  3. 读取第一个逻辑块bh = bread((*dir)->i_dev,block)
  4. 使数据块可以按目录项来遍历de = (struct minix_dir_entry *) bh->b_data;,此时的de是第一个目录项的地址
  5. 利用目录项的数目entries开始遍历第一个数据块的目录项,如果当前目录项de对应名字和name匹配即minix_match(namelen,name,de),则返回该目录项所在高速缓冲区的头部,以及将de赋值给res_dir。如果不匹配,则de++,并且如果de已经超出当前逻辑块,则根据当前已遍历的目录项数目i求得新的逻辑块号然后根据bmap得到该逻辑块号对应物理块号block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK),然后读入该物理块并且使数据块可以按目录项来遍历。

namei.c—/fs/namei.c

sys_mknod(const char * filename, int mode, int dev)

返回值:int

作用:可以基于任意文件系统创建设备文件(体现VFS)。与sys_creat(创建普通文件,底层调用的是sys_open)不同在于sys_mknod可以输入设备号参数即dev。

实现:

  1. 判断是否为超级用户suser(),不是则不执行直接返回
  2. 获取最底层目录的inode即
    dir = dir_namei(filename,&namelen,&basename),
    basename被赋值为最底层的目录名或者文件名(比如filename是/dev/usb,则dir是dev/目录的inode,basename是usb)
  3. 判断dir的写权限,无则不执行直接返回
  4. 判断dir是否有mknod的函数指针即
    if (!dir->i_op || !dir->i_op->mknod),无则不执行直接返回
  5. 调用dir的mknod函数(体现VFS)即dir->i_op->mknod(dir,basename,namelen,mode,dev);(假如磁盘是minix文件系统,那么dir->i_op->mknod就是minix_mknod)

bitmap.c—/fs/minix/bitmap.c

minix_new_block(int dev)

返回值:int

作用:从设备号为dev的设备中找到空闲的数据块并返回该块在整个设备的块号

实现:

  1. 从dev设备中获取超级块即sb = get_super(dev)
  2. 通过遍历dev设备的数据块位图,找到空闲的数据块即j=find_first_zero(bh->b_data)然后置为占用(且缓冲区设为脏)
  3. 获取空闲数据块在整个设备中的全局编号j += i*8192 + sb->s_firstdatazone-1;
  4. 读入该空闲数据块即bh=getblk(dev,j)
  5. 将缓冲区的空闲数据块清0即clear_block(bh->b_data),并将缓冲区设为脏
  6. 返回空闲数据块在整个设备中的全局编号j

minix_new_inode(int dev)

返回值:inode *

作用:在minix文件系统下,在磁盘找到空闲的inode并初始化

实现:

  1. 从inode缓冲表中获得一个空闲inode即inode=get_empty_inode()
  2. 根据参数dev即设备号初始化得到的inode的超级块指针即
    inode->i_sb = get_super(dev)
  3. 根据刚刚得到的超级块指针可以得到8个inode位图的缓冲区地址(minix文件系统的磁盘的inode位图和数据块位图均占用8个缓冲块即8M),依次遍历这8个缓冲区,对于每个缓冲区遍历每个位 直到找到第一个为0的位即find_first_zero(bh->b_data)相当于找到磁盘中空闲的inode
  4. 将刚刚在inode位图中找到的空闲位设置为1,即被占用
  5. 将缓冲区设置为脏使得刚刚在缓冲区的inode位图的空闲位设置为1能写回磁盘即bh->b_dirt = 1;
  6. 初始化该inode,inode设置为脏,inode的全局编号(即在磁盘的inode数组的第几个)根据3也可以确定下来,其中最重要的是将minix文件系统对inode的操作函数指针赋值给该inode,即inode->i_op = &minix_inode_operations;

inode.c

minix_write_inode(struct inode * inode)

返回值:inode *

作用:将传入的minix文件系统的inode标记为脏

实现:

  1. 找到传入的inode所在的物理磁盘块(
    block =
    2 +
    inode->i_sb->s_imap_blocks + inode->i_sb->s_zmap_blocks +
    (inode->i_ino-1)/
    MINIX_INODES_PER_BLOCK;)
    并且通过bh=bread(inode->i_dev,block)读入这个块,然后根据bh->data得到该minix_inode即(raw_inode =
    ((struct minix_inode *)bh->b_data) +
    (inode->i_ino-1)%MINIX_INODES_PER_BLOCK;)
    这里必须要先把inode所在块读入而不是直接写inode到磁盘,因为内存与磁盘的交互是缓冲块,只有inode这一小部分的内容(还不够一个块的大小)那直接写回就会丧失了其余部分
  2. 将传入的通用inode中的属性赋值给minix_inode的对应属性(由于raw_inode是bh->b_data的地址,所以raw_inode被赋值的时候缓冲区内容也被修改了)
  3. 如果传入的inode是设备文件的(块设备文件或者字符设备文件),则只需要赋值首个块号即raw_inode->i_zone[0] = inode->i_rdev;(inode有i_dev和i_rdev,对于普通文件的i_dev表明该文件所在磁盘的设备号,对于设备文件的i_rdev表明该设备的设备号)。否则(普通文件的inode)需要赋值每个索引块指明文件所在物理块的位置。
  4. 将缓冲区设置为脏即bh->b_dirt=1(原因如第2点),将inode设置为未修改即inode->i_dirt=0(因为已经为该inode修改完成)

minix_create_block(struct inode * inode, int block)

返回值:int

作用:在inode中获取第block个逻辑块的物理块,如果发现该逻辑块还没有被分配物理块,则通过遍历数据块位图找到空闲的物理块分配给该逻辑块

实现:

  1. 调用_bmap(inode,block,1);

buffer.c

brelse(struct buffer_head * buf)

作用:当前进程释放对缓冲区的引用

实现:

  1. 等待缓冲区解锁即wait_on_buffer(buf);
  2. 引用数减1即buf->b_count–
  3. 唤醒缓冲区等待队列的首进程wake_up(&buffer_wait);

wait_on_buffer(struct buffer_head * bh)

作用:令当前进程等待缓冲区资源。如果缓冲区无人占用则无需等待,否则将当前进程加入到该资源的等待队列并阻塞

实现:

  1. 关中断,确保执行2的时候只有一个进程访问资源的锁,否则一旦锁空闲,所有进程都不阻塞了
  2. 循环判断资源是否被上锁占用了,是则将当前进程加入到该资源的等待队列并阻塞即
    while (bh->b_lock) sleep_on(&bh->b_wait);
  3. 开中断

bread(int dev,int block)

返回值:buffer_head *

作用:将设备号为dev的设备中的第block块数据读入到缓冲区并返回该缓冲区

实现:

  1. 若指定块之前已经被读入到某个缓冲区,则直接返回;否则找到一个空闲缓冲区并用dev和block初始化即bh=getblk(dev,block)
  2. 如果bh->b_uptodate==1表明缓冲区中的内容是最新的,则直接返回
  3. 否则读入该块以更新缓冲区的内容即ll_rw_block(READ,bh);
  4. 等待缓冲区解锁即wait_on_buffer(bh);
  5. 返回缓冲区

BADNESS宏定义

定义:BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)

返回值:char(但是数字)

作用:分配空闲缓冲区时衡量缓冲区的好坏(空闲)程度。同样没上锁的两个缓冲区,被修改过的那个是更坏的缓冲区(2)。同样没被修改过的两个缓冲区,上锁的那个是更坏的缓冲区(1)。既被上锁还被修改过则是最坏的缓冲区(3),既没被上锁也没被修改过则是最好的缓冲区(0)(b_dirt和b_lock的值只会为0或1)。

getblk(int dev,int block)

返回值:buffer_head *

作用:返回装有设备号为dev的设备中块号为block的内容的缓冲区

实现:

  1. 通过哈希值看指定块是否已存在即
    bh = get_hash_table(dev,block),bh是最终要返回的缓冲区
  2. 循环遍历空闲缓冲区链表(双向链表)free_list,如果当前缓冲区的被引用数b_count>0,则说明缓冲区正在被使用(???那为什么还把它放进空闲缓冲区链表???),看下一个空闲缓冲区。如果当前缓冲区空闲且在此之前未找到空闲缓冲区即bh==NULL,或者当前缓冲区比已找到的空闲缓冲区bh(BADNESS(tmp)
  3. 如果遍历完后bh仍为空,说明没有空闲缓冲区或者所有缓冲区的引用数都>0,则当前进程阻塞等待即加入到空闲缓冲区资源的等待队列的队首sleep_on(&buffer_wait),直到被唤醒(有空闲缓冲区了),则再次回到2执行(goto语句)
  4. 到了这里说明已经找到空闲缓冲区了即bh!=NULL,开始等待缓冲区解锁即wait_on_buffer(bh)(???我对这里的空闲的定义很模糊???我猜测free_list链表不一定全是空闲缓冲区,甚至所有缓冲区都一直在free_list中,只是被占用会改变它在链表中的位置,比如会靠后一些)
  5. 如果被唤醒后缓冲区引用数>0,则再次回到2执行
  6. 循环检查缓冲区是否被修改过,是则将其写回设备,并再次等待缓冲区解锁,然后执行5,然后再次检查
  7. 执行到这里的时间已经过去很久,可以通过遍历检查是否其他进程已经读入了find_buffer(dev,block),是则返回
  8. 初始化得到的空闲缓冲区。占用该缓冲区即bh->b_count=1,不必写回磁盘即bh->b_dirt=0,未更新bh->b_uptodate=0;
  9. 从缓冲区哈希链表数组hash_table和空闲链表freelist中移除该缓冲区(二者都是链表移除节点的方式)即remove_from_queues(bh);
  10. 初始化缓冲区对应的设备号和块号即bh->b_dev=dev;bh->b_blocknr=block;
  11. 将该缓冲区重新插入到空闲链表的尾部以及哈希链表数组的头部

ll_rw_block.c

ll_rw_block(int rw, struct buffer_head * bh)

作用:根据读写标记rw在缓冲区和块设备之间进行读或写(一个缓冲区的内容)

实现:

  1. 检查主设备号是否超出系统自带的块设备管理数目即NR_BLK_DEV,是则直接返回
  2. 根据主设备号检查当前设备在系统自带的块设备管理数组中是否有对应请求函数即request_fn,是则直接返回
  3. 向设备发起请求即make_request(major,rw,bh);

make_request(int major,int rw, struct buffer_head * bh)

作用:从全局request数组找到空闲项然后创建设备读写请求项并插入设备请求队列

实现:

  1. 对缓冲区上锁即lock_buffer(bh);
  2. 如果读写标记rw为写而缓冲区未被修改过(即rw == WRITE && !bh->b_dirt)则直接解锁返回
  3. 如果读写标记rw为读而缓冲区与磁盘同步(即rw == READ && bh->b_uptodate)则直接解锁返回
  4. 若rw是读,则将临时请求指针变量req指向全局request数组的末尾即req = request+NR_REQUEST,若rw是写,则指向3分之2处即req = request+((NR_REQUEST*2)/3),用于等一下的自后向前遍历(对于全局request数组即struct request request[NR_REQUEST],后3分之1属于读请求,前面的3分之2属于写请求)
  5. 自后向前遍历request数组即while (–req >= request),找到空闲的request项即req->dev<0则break(因为读请求从最后一项开始遍历,因此体现出读优先,更容易找到空闲项)
  6. 如果没找到空闲request项即req < request则当前进程阻塞并加入到等待队列(本质是栈)即sleep_on(&wait_for_request),阻塞完成后才回到2重新继续向下执行
  7. 找到空闲request项后开始根据缓冲区进行初始化即req->dev = bh->b_dev,req->cmd = rw,req->buffer = bh->b_data,req->waiting = NULL,req->bh = bh,
    req->next = NULL,因为linux中1个逻辑块等于2个扇区即req->nr_sectors = 2,所以当前逻辑块所在块号乘2等于所在扇区即req->sector = bh->b_blocknr<<1
  8. 将读写请求项插入到设备请求队列即add_request(major+blk_dev,req)

add_request(struct blk_dev_struct * dev, struct request * req)

作用:使用C-SCAN电梯算法将当前请求req请求插入到请求队列

实现:

  1. 关中断,保证读写请求队列的互斥
  2. 将对应缓冲区的脏位清空即req->bh->b_dirt = 0;
  3. 判断当前要使用的设备是否已经有请求了即
    if (!(tmp = dev->current_request)),没有则令当前请求作为设备的当前请求(第一个请求)即dev->current_request = req,然后开中断,调用块设备的具体的请求处理函数(dev->request_fn)(),linux0.95提前为硬盘、软盘、虚拟硬盘、tty提供了请求处理函数和驱动(比如硬盘的则在系统初始化的时候调用hd_init将本就写好的请求处理函数注册到blk_dev数组的硬盘项)
  4. 如果目前该设备已经有请求项在等待,则tmp相当于第一个请求项,从tmp开始向后遍历,如果req->sector < tmp->next->sector < tmp->sector
    (sector是扇区号)或者
    tmp->sector < req->sector < tmp->next->sector,则直接跳出循环然后插入到tmp和tmp->next之间。
    核心思想是:如果当前遍历到的tmp的下一步在向小的方向(temp->next)移动且req的比它们两都更小,则tmp下一步去req,req下一步才去next; 如果当前遍历到的tmp的下一步在向大的方向移动且req的恰好在它们两的之间,则tmp下一步去req,req下一步去next
    磁臂初始移动方向(教科书通常说规定为某某方向)在linux的实现中由磁臂当前停留的磁道和进来的第一个请求决定
  5. 开中断

read_write.c

sys_write(unsigned int fd,char * buf,unsigned int count)

返回值:int

作用:

实现:
1.
2.

hd.c

hd_init()

作用:系统初始化时初始化硬盘相关的内容

实现:

  1. 向内存中的块设备数组注册硬盘的请求处理函数即blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST该宏定义即do_hd_request(),MAJOR_NR在hd.c中是3(在ramdisk.c虚拟硬盘是1,floppy.c软盘是2),DEVICE_REQUEST也是同理
  2. 设置磁盘中断号及其处理函数set_intr_gate(0x2E,&hd_interrupt);
  3. 往几个 IO 端口上读写即outb,允许硬盘控制器发送中断请求信号

do_hd_request()

作用:处理硬盘读写请求队列中的当前(首个)请求

实现:

  1. 检查当前要处理的第一个请求合法性,比如检查主设备号是否为硬盘主设备号3,检查请求对应的缓冲区是否上好锁了,没锁旧调用panic使之死机
  2. 获取当前请求的次设备号即dev = MINOR(CURRENT->dev),以及请求的扇区号即block = CURRENT->sector(这里的block可能会有误解,把它理解为扇区号就是了)
  3. 判断次设备号是否超出分区数,以及扇区号是否超出倒数第二个扇区(因为linux按块为单位读取,而一个块是两个扇区大小,所以不能超过倒数第二个扇区)即if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects),超出则终止请求即end_request(0);
    (在系统初始化时会调用sys_setup(void * BIOS),其中会根据硬件检测来初始化硬盘数目即NR_HD以及各硬盘的分区情况即struct hd_struct hd[5*MAX_HD],其中MAX_HD为2,每块磁盘占用5个项,其中的第1项代表整个硬盘的物理起始扇区号、分区扇区总数(物理磁盘),2~5项代表各个分区的物理起始扇区号、分区扇区总数(可以理解为4个逻辑磁盘),一个硬盘的各个分区分别使用不同的次设备号,整个硬盘也要单独占用一个次设备号,linux0.95最多允许使用两个硬盘,它们的主设备号相同(3))
  4. 将当前请求的扇区号加上dev对应分区的起始扇区号即block += hd[dev].start_sect;
  5. 根据次设备号求得其属于第几块硬盘即dev /= 5,根据3的判断可知次设备号的范围是0~9,其中0~4分配给第二块硬盘及其分区,5~9分配给第二块硬盘及其分区
  6. 请求所在扇区号block除以每磁道扇区数hd_info[dev].sect,得到商放回block,余数放在sec;再将刚才的block除以硬盘磁头数hd_info[dev].head,得到商放在cyl,余数放在head。
    (我们只需要给出磁头号,柱面号以及所在柱面的扇区号,磁盘控制器即可帮助我们找到指定扇区,需要注意,这里的所在柱面的 扇区 和指定 扇区 不同,指定扇区的编号由请求的块号乘2得来,所在柱面的扇区号在1~hd_info[dev].sect,即不同盘面的扇区号都是从1~hd_info[dev].sect编号。我们之前在操作系统层面上将磁盘抽象为多个逻辑块,直觉上相邻的逻辑块(扇区)也应该快速地访问到,于是我们想到以下的全局编号(相对于磁盘每个盘面局部地为扇区编号)方式,将逻辑上的n个块即2n个扇区的逻辑扇区号0~2n-1映射到磁盘上的扇区

linux源码通俗解读_第8张图片
linux源码通俗解读_第9张图片假设先使用完里面的柱面,再使用外面的。
每个盘面都分别有磁头
如上图,0到6号扇区是连续的,这无需寻道的开销,6到7号扇区也是连续的,即比如要同时访问6和7号扇区,刚访问完6号,只需继续旋转,仍不需要寻道,只是此时由第二个(自上而下数)磁头读取内容
根据这种编号方式,我们可以得到上图中的红色公式(其中Heads是磁头数目,Sectors是每磁道扇区数),即假设已知磁盘原生访问方式的柱面号C(磁道号)、磁头号H、扇区号S,如何求出逻辑扇区号(逻辑块号×2),这样通过倒推,我们又可以知道如何通过逻辑扇区号知道柱面号、磁头号、扇区号。
比如对于扇区号,
因为
b l o c k = ( C × H e a d s + H ) × S e c t o r s + S block=(C \times Heads + H) \times Sectors + S block=(C×Heads+H)×Sectors+S
于是有
S = b l o c k % S e c t o r s S=block \% Sectors S=block%Sectors
这里需要注意,S 此时令
b l o c k = b l o c k / S e c t o r s = C × H e a d s + H block=block/Sectors=C \times Heads + H block=block/Sectors=C×Heads+H
于是有
C = b l o c k / h e a d s C=block/heads C=block/heads
又有
H = b l o c k % h e a d s H=block\%heads H=block%heads
同理前提是H )
7. 实际上磁盘原生访问方式的扇区号从1开始,所以求得的扇区号最后要加1
8. 要读写的扇区数nsect = CURRENT->nr_sectors,其实就是2
9. 如果是写请求即CURRENT->cmd == WRITE,向硬盘控制器端口发送写命令即hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr),其实就是根据寄存器所在地址以及要填充的内容来调用outb,其中&write_intr是执行完写操作后发生中断,然后中断处理函数内调用的函数的地址(反正就是中断后会被执行的函数)。然后循环3000次不断读入硬盘的状态位,等待硬盘准备好接收数据即
for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++),如果r一直为0,表明在规定时间内磁盘仍未准备好,则调用读写硬盘失败处理函数即bad_rw_intr。准备好则在汇编层面循环(rep)不断地向同一个数据端口HD_DATA输出缓冲区的内容(????),每次输出2个字节(outsw),重复256次就刚好是512字节即半个缓冲区一个扇区的大小即port_write(HD_DATA,CURRENT->buffer,256)的底层实现,处理完一个扇区后发起中断间接调用到write_intr,于是会再一次调用port_write,直至–CURRENT->nr_sectors=0为止
10. 如果是读请求则只需要向硬盘控制器端口发送读命令即hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);然后等着中断到来就好了,届时会进入到read_intr函数处理

write_intr

作用:每写完一个扇区后的中断处理函数(确切地说是中断处理函数调用到了这个函数,磁盘中断发生并不是直接调用该函数的,而是通过一些分支间接调用到了)

实现:

  1. 检查硬盘执行命令后的状态,0为ok,1为出错并return
  2. 判断是否还有要写的扇区即if (–CURRENT->nr_sectors),由于一个块=一个缓冲区=2个扇区,所以通常这个if成立,于是进行第二次读写扇区,先修改当前请求的信息即CURRENT->sector++;CURRENT->buffer += 512,然后再一次发送写命令port_write(HD_DATA,CURRENT->buffer,256)并return
  3. 如果当前请求已经处理完,没有还要写的扇区,则为当前请求善后即end_request(1),将请求对应的缓冲区的更新位置为1,然后解锁该缓冲区,唤醒等待该请求项的进程队列即wake_up(&CURRENT->waiting)以及等待空闲请求项的进程队列即wake_up(&wait_for_request);,将当前请求项的dev设为-1表示请求项空闲,并且更新当前块设备的当前请求即CURRENT = CURRENT->next;
  4. 处理新请求即do_hd_request();

read_intr

作用:每读完一个扇区后的中断处理函数,具体地说,是发送读命令给磁盘控制器后,磁盘会将一个扇区的数据送到它自己的缓冲区,并发起一次中断然后调用到read_intr

实现:

  1. 检查硬盘执行命令后的状态,0为ok,1为出错并return
  2. 将磁盘缓冲区中的一个扇区(512字节)的内容读到buffer中即port_read(HD_DATA,CURRENT->buffer,256);
  3. 缓冲区偏移为下次磁盘缓冲区数据到达而读数据作准备即CURRENT->buffer += 512; 扇区号加1即CURRENT->sector++,使得即使当前读扇区发生错误,重试的时候还可以继续(有重试次数限制)
  4. 判断是否还有要写的扇区即if (–CURRENT->nr_sectors)

sys_setup(void * BIOS)

返回值:int

作用:

实现:

  1. 根据系统提供的BIOS地址(里面是通过检测得到的各种硬件参数)读入其中硬盘的参数到hd_info全局硬盘参数数组(只有2项)中,比如
    hd_info[drive].cyl = *(unsigned short *) BIOS。如果只有一个硬盘,那么第二个硬盘的参数全设0
    (理论上可以直接修改config.h头文件,在其中指明所使用的硬盘参数,比如定义两个硬盘的参数#define HD_TYPE { h,s,c,wpcom,lz,ctl },{ h,s,c,wpcom,lz,ctl },那么全局硬盘参数数组struct hd_i_struct hd_info[] = { HD_TYPE }被修改为我们定义的参数,不必由系统来检测了)
  2. 根据(1)第二个硬盘参数是否为0可知电脑中的硬盘个数即NR_HD
  3. 分别初始化NR_HD个硬盘的物理盘的起始扇区号(均设0)和扇区总数即hd[i*5].nr_sects =
    hd_info[i].head*hd_info[i].sect*hd_info[i].cyl,每个硬盘占用5项,第一项是物理盘的参数,二~五项是逻辑盘的参数
    struct hd_struct {
    long start_sect;
    long nr_sects;
    } hd[5*MAX_HD]={{0,0},};
  4. 读取(bh = bread(0x300 + drive*5,0))每一个硬盘上第 1 块数据(要用其中的第 1 个扇区),判断第一个扇区的0x1fe 处的两个字节是否为’55AA’作为分区表是否正常的依据即if(bh->b_data[510] != 0x55||
    bh->b_data[511] != 0xAA)
    然后指向扇区中0x1BE开始的分区表.
    即p = 0x1BE + (void *)bh->b_data,用来填充hd数组的4项(每个分区相当于一个逻辑盘)即各分区起始扇区及扇区数
    (因为硬盘主设备号都是3,放在3、4字节,所以是0x300,物理盘和4个逻辑盘各占一个次设备号,所以第二个硬盘的物理盘的次设备号是5(因为0~4被第一块硬盘占用))
  5. 加载(创建)虚拟硬盘RAMDISK即rd_load();
  6. 安装根文件系统即mount_root()

fault.c

__do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,struct task_struct *tsk)

返回值:int

作用:处理缺页中断

实现:
1.根据给定虚拟地址addr(计算完分段后的地址)查找满足如下条件之一的VMA(VMA同时存在两种组织形式,双向链表和红黑树,在这里是通过红黑树来查找的即O(logN)),如下图
linux源码通俗解读_第10张图片
即vma = find_vma(mm, addr),如果find_vma()找不到vma,说明addr地址还没有在进程地址空间中,返回VM_FAULT_BADMAP错误,再上一层函数即do_page_fault()收到这个错误码会将进程终止
2. 如果虚拟地址addr小于栈的基地址,则说明有可能处于栈的虚拟空间附近(在栈空间中或之外),即vma->vm_start > addr,则以页为单位开辟新的栈的虚拟空间即expand_stack(vma, addr),expand_stack只是更改了堆栈区的vm_area_struct结构,没有建立物理内存映射
3. 能到这一步的,说明addr在某个段的虚拟空间中,或者在栈的虚拟空间附近并且刚刚在2完成了栈的扩充,此时通过access_error()判断VMA是否具备可写或可执行即参数fsr要求的权限,不具备则直接返回VM_FAULT_BADACCESS错误
4. 此时排除了权限导致的缺页中断,调用handle_mm_fault(mm, vma, addr & PAGE_MASK, flags)处理各级页表未建立映射关系以及页面在交换分区导致的缺页中断

memory.c—mm/memory.c

handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma,unsigned long address, int write_access)

返回值:int

作用:处理各级页表未建立映射关系以及页面在交换分区导致的缺页中断。此处应该是四级(个)页表,五次映射运算。注意,每个页表的项放的都是物理地址

实现:

  1. 根据虚拟地址address以及当前进程的内存描述符,在顶级页目录获取该虚拟地址对应的二级页目录的虚拟地址即pgd = pgd_offset(mm, address),如果pgd为空,则返回VM_FAULT_OOM错误
  2. 在二级页目录获取该虚拟地址对应的三级页目录的虚拟地址即pud = pud_alloc(mm, pgd, address),如果pgd为空,则先为pgd申请分配一个内容全0的页然后指向该页地址(即二级页目录,分配不成功返回VM_FAULT_OOM错误,因为这表示没内存分配新页),然后再根据address返回在新二级页目录中的三级页目录的虚拟地址(得到的三级页目录必然为0,即三级页目录物理地址为0,不过要求的是其虚拟地址,所以会加上一个偏移量,因为虚拟地址空间的内核空间和物理地址空间的内核空间位置正好相反,即一个在顶着高地址,一个顶着低地址0,只需要加偏移量就可以完成 物理地址->虚拟地址,总之得到的虚拟地址不等于0)。如果pgd非空则直接根据address返回在新二级页目录中的三级页目录的虚拟地址
  3. 在三级页目录获取该虚拟地址对应的四级页目录地址即pud = pud_alloc(mm, pgd, address),如果pmd为空或者非空,则都和2的处理一样
  4. 获取该虚拟地址对应的最底层页表的项的地址即pte = pte_alloc_map(mm, pmd, address),如果pte为空或者非空,则都和2的处理一样(pmd变量指向最低层的页表即pte,pte每一项指的都是物理页的地址pte了,确切地说,pte一部分bit是地址,另一部分bit是权限属性)
  5. 到了这里,已经得到了该虚拟地址的对应物理页的地址和一些属性权限信息,各级页表的映射大致完成,需要注意,该物理页的地址可能是全0虚拟化后的地址(即物理页未分配),在handle_pte_fault(mm, vma, address, write_access, pte, pmd)中会进一步处理

因为程序都是虚拟地址,都要进行页表映射才能找到物理地址,要想修改页表则程序中仍需要用虚拟地址。所有页表都存放在物理内存的内核空间中,
linux源码通俗解读_第11张图片

handle_pte_fault(struct mm_struct *mm,struct vm_area_struct * vma, unsigned long address,int write_access, pte_t *pte, pmd_t *pmd)

返回值:int

作用:

实现:

  1. 首先判断当前页表项的present位,如果为0,则表明未分配物理页或者分配了物理页但存在于磁盘的交换分区,其中未分配物理页分为两种情况,分别是平常的未分配物理页(如malloc,由do_no_page(mm, vma, address, write_access, pte, pmd)进一步处理,不到2)和与文件关联的未分配物理页(如mmap,由do_file_page(mm, vma, address, write_access, pte, pmd)进一步处理,不到2),如果是物理页存在于磁盘的交换分区,则由do_swap_page(mm, vma, address, pte, pmd, entry, write_access)进一步处理,不到2
  2. 到这里说明页表项是正常的,即物理页已分配且就在物理内存中,此时如果发生缺页中断的指令为写指令且页表项指向的页不允许写,则由do_wp_page(mm, vma, address, pte, pmd, entry)进一步处理(wp是write on copy写时拷贝,比如fork的时候子进程会复制父进程的页表并且把父进程的物理页都修改成只读,本质上二者在共享一片物理内存,直到父或子写某个物理页A则会触发这个缺页中断,do_wp_page重新申请一个物理页B然后复制A的内容并把新写的内容更新上去,此时开始父子页表开始有一点不同,),不到3。如果发生缺页中断的指令为写指令而页表项指向的页允许写,则将该页修改为脏页即entry = pte_mkdirty(entry)
  3. 修改为最近被读写过(因为马上就要被读写,用于LRU算法对内存页的换入换出)entry = pte_mkyoung(entry),(什么样的缺页中断会执行到这里?????)

do_no_page(struct mm_struct *mm, struct vm_area_struct *vma,unsigned long address, int write_access, pte_t *page_table, pmd_t *pmd)

返回值:int

作用:为读写某个虚拟地址而其未被分配对应物理页而产生的缺页中断分配物理页

实现:

  1. 所有请求分配物理页的进程都会先指向同一个写保护(只读)的全零页(即empty_zero_page,该页存在于内核空间)即entry = pte_wrprotect(mk_pte(ZERO_PAGE(addr), vma->vm_page_prot));
  2. 如果是未分配物理页且尝试读而触发的缺页中断(比如刚malloc了一个变量i然后立刻访问i),则直接返回1得到的全零页,(那i的值为0才对呀但为什么不是????)。如果是未分配物理页且尝试写而触发的缺页中断,则申请一个新的物理页

namespace.c—fs/namespace.c

sys_mount(char __user * dev_name, char __user * dir_name, char __user * type, unsigned long flags,void __user * data)

返回值:long

作用:挂载外部存储设备的文件系统到根文件系统中,
mount -t ext4 /dev/cdrom /mnt将文件系统已被格式化为ext4的设备/dev/cdrom挂载到/mnt目录下
(通常挂载之前都要先格式化外部存储设备的文件系统为当前os支持的文件系统,即命令mkfs -t ext4 /dev/sdb1)

实现:

  1. (1)将dev_name即块设备文件所在路径,dir_name即挂载点所在路径,type即文件系统类型三个参数拷贝到内核空间
    (2)调用do_mount函数,传入上述三个参数以及flags挂载标志,data挂载选项
sys_mount(mount, char __user *, dev_name, char __user *, dir_name,
		char __user *, type, unsigned long, flags, void __user *, data)
{
	int ret;
	char *kernel_type;
	char *kernel_dev;
	void *options;
  /*1 拷贝文件系统类型名到内核空间 */
	kernel_type = copy_mount_string(type);
	ret = PTR_ERR(kernel_type);
	if (IS_ERR(kernel_type))
		goto out_type;
  /*2 拷贝块设备路径名到内核空间 */
	kernel_dev = copy_mount_string(dev_name);
	ret = PTR_ERR(kernel_dev);
	if (IS_ERR(kernel_dev))
		goto out_dev;
  /*3 拷贝挂载选项到内核空间 */
	options = copy_mount_options(data);
	ret = PTR_ERR(options);
	if (IS_ERR(options))
		goto out_data;
  /*4 挂载委托do_mount,最重要的接口实现 */
	ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);

	kfree(options);
out_data:
	kfree(kernel_dev);
out_dev:
	kfree(kernel_type);
out_type:
	return ret;
}

  1. (1)根据挂载点路径查找挂载点信息从而解析成该挂载点对应的path结构体
    (2)然后通常调用的是do_new_mount(因为通常某个挂载点都是第一次被挂载,如果是重新挂载或者其他情况则调用其他函数)
do_mount
long do_mount(const char *dev_name, const char __user *dir_name,
		const char *type_page, unsigned long flags, void *data_page)
{
	struct path path;                      //path结构体实例
	int retval = 0;
	int mnt_flags = 0;                     //挂载标记

	//去掉标记参数中的魔数
	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
		flags &= ~MS_MGC_MSK;

	/* 安全性检查 */
	if (data_page)
		((char *)data_page)[PAGE_SIZE - 1] = 0;

	/*1 根据挂载点路径查找挂载点信息, 把挂载点解析成path结构体 */
	retval = user_path(dir_name, &path);  //path保存path结构体信息
	if (retval)
		return retval;

	retval = security_sb_mount(dev_name, &path,
				   type_page, flags, data_page);
	if (!retval && !may_mount())
		retval = -EPERM;
	if (!retval && (flags & MS_MANDLOCK) && !may_mandlock())
		retval = -EPERM;
	if (retval)
		goto dput_out;

	/* Default to relatime unless overriden */
	if (!(flags & MS_NOATIME))
		mnt_flags |= MNT_RELATIME;

	/*挂载标记参数转成内核内部标记, 分割每个挂载点的挂载标志 */
	if (flags & MS_NOSUID)
		mnt_flags |= MNT_NOSUID;
	if (flags & MS_NODEV)
		mnt_flags |= MNT_NODEV;
	if (flags & MS_NOEXEC)
		mnt_flags |= MNT_NOEXEC;
	if (flags & MS_NOATIME)
		mnt_flags |= MNT_NOATIME;
	if (flags & MS_NODIRATIME)
		mnt_flags |= MNT_NODIRATIME;
	if (flags & MS_STRICTATIME)
		mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
	if (flags & MS_RDONLY)
		mnt_flags |= MNT_READONLY;

	/* 默认的重新挂载时间是保存时间 */
	if ((flags & MS_REMOUNT) &&
	    ((flags & (MS_NOATIME | MS_NODIRATIME | MS_RELATIME |
		       MS_STRICTATIME)) == 0)) {
		mnt_flags &= ~MNT_ATIME_MASK;
		mnt_flags |= path.mnt->mnt_flags & MNT_ATIME_MASK;
	}

	flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |
		   MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
		   MS_STRICTATIME | MS_NOREMOTELOCK | MS_SUBMOUNT);

	if (flags & MS_REMOUNT)   // 如果标志位是重新挂载
		retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
				    data_page);
	else if (flags & MS_BIND) // 通过环回接口挂载一个文件系统
		retval = do_loopback(&path, dev_name, flags & MS_REC); 
	else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
		retval = do_change_type(&path, flags); // 处理共享、从属和不可绑定挂载操作
	else if (flags & MS_MOVE) //移动一个已经挂载的文件系统
		retval = do_move_mount(&path, dev_name);
	/*2 为该挂载点执行新的挂载操作*/
	else  
		retval = do_new_mount(&path, type_page, flags, mnt_flags,
				      dev_name, data_page);
dput_out:
	path_put(&path);
	return retval;
}

  1. (1)根据文件系统类型名在file_system_type类型的全局链表遍历查找对应file_system_type实例
    (2)然后根据得到的file_system_type实例调用vfs_kern_mount函数(主要完成创建和初始化该文件系统对应的超级块super_block、根目录项dentry和inode结构体实例,创建mount结构体实例并建立各结构体实例之间的关联即指针指向),其底层是调用具体文件系统的mount函数
    (3)调用关联挂载点函数do_add_mount()建立mount和挂载点mountpoint实例、挂载点dentry实例之间的关联,并将mount实例插入全局散列链表头部,挂载操作完成
do_new_mount
static int do_new_mount(struct path *path, const char *fstype, int flags,
			int mnt_flags, const char *name, void *data)
{
	struct file_system_type *type;     //文件系统类型
	struct vfsmount *mnt;
	int err;

	if (!fstype)
		return -EINVAL;
	/*1 根据文件系统类型名在file_system_type类型的全局链表遍历查找对应file_system_type实例 */
	type = get_fs_type(fstype);    
	if (!type)
		return -ENODEV;
	/*2 根据文件系统类型即刚才1得到的file_system_type实例调用具体文件系统的mount函数 */
	mnt = vfs_kern_mount(type, flags, name, data);
	if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
	    !mnt->mnt_sb->s_subtype)
		mnt = fs_set_subtype(mnt, fstype);

	put_filesystem(type);
	if (IS_ERR(mnt))
		return PTR_ERR(mnt);

	if (mount_too_revealing(mnt, &mnt_flags)) {
		mntput(mnt);
		return -EPERM;
	}
	/*3 关联挂载点 */
	err = do_add_mount(real_mount(mnt), path, mnt_flags);
	if (err)
		mntput(mnt);
	return err;
}

vfs_kern_mount

(1)根据设备文件名name从缓存分配mount实例并初始化各成员
(2)调用具体文件系统类型定义的挂载函数并返回该具体外部文件系统的根目录的dentry项root
(3)建立mount实例与super_block、dentry实例之间的关联
(4)返回mount实例的vfsmount结构体

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
	struct mount *mnt;
	struct dentry *root;

	if (!type)
		return ERR_PTR(-ENODEV);//
	/*1 从slab缓存分配mount实例,分配ID号,并初始化各成员 */
	mnt = alloc_vfsmnt(name);
	if (!mnt)
		return ERR_PTR(-ENOMEM);
	if (flags & MS_KERNMOUNT)
		mnt->mnt.mnt_flags = MNT_INTERNAL;
	/*2 调用具体文件系统类型定义的挂载函数 */
	root = mount_fs(type, flags, name, data);
	if (IS_ERR(root)) {
		mnt_free_id(mnt);
		free_vfsmnt(mnt);
		return ERR_CAST(root);
	}
	/*3 建立mount实例与super_block、dentry实例之间的关联 */
	mnt->mnt.mnt_root = root;                  //mount内部的vfsmount的mnt_root指向挂载的外部文件系统根目录项dentry实例
	mnt->mnt.mnt_sb = root->d_sb;              //指向外部文件系统超级块实例
	mnt->mnt_mountpoint = mnt->mnt.mnt_root;   //指向挂载的外部文件系统根目录项dentry实例,在关联挂载点时将重新赋值,指向内核根文件系统中挂载点dentry实例
	mnt->mnt_parent = mnt;                     //父mount实例指向自身

	lock_mount_hash();
	list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);  //插入超级块中链表的末尾
	unlock_mount_hash();
	return &mnt->mnt;   //返回mount实例mnt成员指针,vfsmount结构体成员
}

vfs_kern_mount()函数创建的数据结构实例组织关系如下图所示:
linux源码通俗解读_第12张图片

mount_fs

(1)调用具体文件系统类型定义的mount()函数,创建并初始化文件系统超级块super_block、根目录项dentry和inode结构体实例,返回挂载的外部文件系统根目录项dentry实例指针

struct dentry *
mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
{
	struct dentry *root;         //返回值,挂载文件系统根目录项的dentry实例指针
	struct super_block *sb;
	char *secdata = NULL;
	int error = -ENOMEM;

	if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {
		secdata = alloc_secdata();
		if (!secdata)
			goto out;

		error = security_sb_copy_data(data, secdata);
		if (error)
			goto out_free_secdata;
	}
	/*1 调用具体文件系统类型挂载函数,创建各数据结构体实例 */
	root = type->mount(type, flags, name, data);
	if (IS_ERR(root)) {
		error = PTR_ERR(root);
		goto out_free_secdata;
	}
	sb = root->d_sb;      /* 文件系统超级块指针 */
	BUG_ON(!sb);
	WARN_ON(!sb->s_bdi);
	sb->s_flags |= MS_BORN;

	error = security_sb_kern_mount(sb, flags, secdata);
	if (error)
		goto out_sb;

	/*
	 * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
	 * but s_maxbytes was an unsigned long long for many releases. Throw
	 * this warning for a little while to try and catch filesystems that
	 * violate this rule.
	 */
	WARN((sb->s_maxbytes < 0), "%s set sb->s_maxbytes to "
		"negative value (%lld)\n", type->name, sb->s_maxbytes);

	up_write(&sb->s_umount);
	free_secdata(secdata);
	return root;     //返回挂载文件系统根目录项dentry实例指针
out_sb:
	dput(root);
	deactivate_locked_super(sb);
out_free_secdata:
	free_secdata(secdata);
out:
	return ERR_PTR(error);
}

例如,ext4文件系统的file_system_type实例定义如下(/fs/ext2/super.c):

static struct file_system_type ext4_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext4",
	.mount		= ext4_mount,        //挂载时调用  用于读取创建超级块实例
	.kill_sb	= kill_block_super,  //卸载时调用  用于释放超级块
	.fs_flags	= FS_REQUIRES_DEV,   //文件系统标志为  请求块设备,文件系统在块设备上
};
MODULE_ALIAS_FS("ext4");

文件系统类型挂载函数ext4_mount()调用了通用的mount_bdev()函数,定义如下(/fs/ext4/super.c):

static struct dentry *ext4_mount(struct file_system_type *fs_type, int flags,
		       const char *dev_name, void *data)
{
	return mount_bdev(fs_type, flags, dev_name, data, ext4_fill_super);
}

函数内直接调用通用的mount_bdev()函数,需要注意的是最后一个参数ext4_fill_super是一个函数指针,mount_bdev()函数内会调用此函数完成超级块实例的填充和初始化,包括dentry和inode实例的创建

  • ext4_fill_super的一个函数指针作为参数传递给get_sb_bdev。该函数用于填充一个超级块对象,如果内存中没有适当的超级块对象,数据就必须从硬盘读取。
  • mount_bdev是个公用的函数,一般磁盘文件系统会使用它来根据具体文件系统的fill_super方法来读取磁盘上的超级块并在创建内存超级块。

我们来看下mount_bdev的实现,它执行完成之后会创建vfs的三大数据结构 super_block、根inode和根dentry

struct dentry *mount_bdev(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data,
	int (*fill_super)(struct super_block *, void *, int))
{
	struct block_device *bdev;
	struct super_block *s;
	fmode_t mode = FMODE_READ | FMODE_EXCL;
	int error = 0;

	if (!(flags & MS_RDONLY))
		mode |= FMODE_WRITE;
  /* 通过要挂载的块设备路径名 获得它的块设备描述符block_device
(会涉及到路径名查找和通过设备号在bdev文件系统查找block_device,
 block_device是添加块设备到系统时创建的) */
	bdev = blkdev_get_by_path(dev_name, mode, fs_type);
	if (IS_ERR(bdev))
		return ERR_CAST(bdev);

	/*
	 * once the super is inserted into the list by sget, s_umount
	 * will protect the lockfs code from trying to start a snapshot
	 * while we are mounting
	 */
	mutex_lock(&bdev->bd_fsfreeze_mutex);
	if (bdev->bd_fsfreeze_count > 0) {
		mutex_unlock(&bdev->bd_fsfreeze_mutex);
		error = -EBUSY;
		goto error_bdev;
	}
  /* 查找或创建vfs的超级 */
	s = sget(fs_type, test_bdev_super, set_bdev_super, flags | MS_NOSEC,
		 bdev);
	mutex_unlock(&bdev->bd_fsfreeze_mutex);
	if (IS_ERR(s))
		goto error_s;
  /*超级块的根dentry是否被赋值*/
	if (s->s_root) {
		if ((flags ^ s->s_flags) & MS_RDONLY) {
			deactivate_locked_super(s);
			error = -EBUSY;
			goto error_bdev;
		}

		/*
		 * s_umount nests inside bd_mutex during
		 * __invalidate_device().  blkdev_put() acquires
		 * bd_mutex and can't be called under s_umount.  Drop
		 * s_umount temporarily.  This is safe as we're
		 * holding an active reference.
		 */
		up_write(&s->s_umount);
		blkdev_put(bdev, mode);
		down_write(&s->s_umount);
	} else {  //没有赋值说明时新创建的sb
		s->s_mode = mode;
		snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev);
		sb_set_blocksize(s, block_size(bdev));  // 根据块设备描述符设置文件系统块大小
    /* 调用传递的具体文件系统的填充超级块方法读取填充超级块等 如ext4_fill_super */
		error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
		if (error) {
			deactivate_locked_super(s);
			goto error;
		}

		s->s_flags |= MS_ACTIVE;
		bdev->bd_super = s;   //块设备bd_super指向sb
	}
   //返回文件系统的根dentry
	return dget(s->s_root);

error_s:
	error = PTR_ERR(s);
error_bdev:
	blkdev_put(bdev, mode);
error:
	return ERR_PTR(error);
}

对于ext4_fill_super主要的工作,如下:

  • 读取磁盘上的超级块
  • 填充并关联vfs超级块
  • 读取块组描述符
  • 读取磁盘根inode并建立vfs 根inode
  • 创建根dentry关联到根inode

  1. 接下来的工作就是通过mount实例建立挂载点dentry实例与挂载文件系统根目录项dentry实例之间的关联,并将mount实例添加到全局散列链表头部。以便将挂载的文件系统导入内核根文件系统,使之对用户进程可见。
    (1)调用lock_mount(path)函数创建(或查找)挂载点mountpoint实例,建立其与挂载点dentry实例的关联,设置挂载点dentry实例DCACHE_MOUNTED标记位(d_set_mounted(dentry)),并将mountpoint实例添加到全局散列表
    (2)调用graft_tree()函数,建立mount实例与mountpoint、挂载点dentry实例之间的关联,并将mount实例插入到全局散列链表的头部,以及加入到内核mount实例的层次(父子关系)结构中
do_add_mount
static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags)
{
	struct mountpoint *mp;
	struct mount *parent;
	int err;

	mnt_flags &= ~MNT_INTERNAL_FLAGS;
	/*1 lock_mount函数创建mountpoint实例,并建立与挂载点dentry关联 */
	mp = lock_mount(path);
	if (IS_ERR(mp))
		return PTR_ERR(mp);
	/*2 vfsmount指针转为mount类型实例指针*/
	parent = real_mount(path->mnt);
	err = -EINVAL;
	if (unlikely(!check_mnt(parent))) {
		/* that's acceptable only for automounts done in private ns */
		if (!(mnt_flags & MNT_SHRINKABLE))
			goto unlock;
		/* ... and for those we'd better have mountpoint still alive */
		if (!parent->mnt_ns)
			goto unlock;
	}

	/*3 避免同一文件系统重复挂载到同一挂载点 */
	err = -EBUSY;
	if (path->mnt->mnt_sb == newmnt->mnt.mnt_sb &&
	    path->mnt->mnt_root == path->dentry)
		goto unlock;

	err = -EINVAL;
	if (d_is_symlink(newmnt->mnt.mnt_root))
		goto unlock;
	/*4 建立mount与mountpoint、挂载点dentry实例关联,并插入散列表*/
	newmnt->mnt.mnt_flags = mnt_flags;
	err = graft_tree(newmnt, parent, mp);

unlock:
	unlock_mount(mp);
	return err;
}

do_add_mount()函数创建的数据结构实例及组织关系如下图所示:
linux源码通俗解读_第13张图片
执行内核的挂载函数vfs_kern_mount:该函数主要是创建文件系统超级块super_block、根目录项dentry和inode结构体实例,并创建表示本次挂载操作的mount结构体实例,mount实例添加到超级块实例s_mounts成员链表中,并与挂载文件系统根目录项dentry建立关联
linux源码通俗解读_第14张图片关联挂载点do_add_mount:创建挂载点mountpoint结构体实例,并添加到全局散列表,mountpoint实例关联到挂载点dentry实例(跟文件系统中目录项),并将挂载mount实例添加到Mountpoint实例链表和全局散列表中,建立mount实例与挂载断点dentry之间的关联,一个挂载点可以有多个挂载,因此Mountpoint实例包含一个挂载mount实例的链表
linux源码通俗解读_第15张图片
执行完这两步,通过mount实例建立了挂载点dentry实例和挂载文件系统根目录项dentry实例之间的联系。
linux源码通俗解读_第16张图片当内核打开文件搜索路径到达挂载点时(挂载点dentry实例设置DCACHE_MOUNTED标记位),将调用函数lookup_mnt(path),在mount实例全局散列表中查找第一个关联到挂载点dentry实例的mount实例,搜索路径随后进入mount实例关联的挂载文件系统根目录项。

end

返回值:

作用:

实现:
1.
2.

结构体

/**/
struct vfsmount {
	struct dentry *mnt_root;	     //指向挂载文件系统根目录项dentry实例
	struct super_block *mnt_sb;	   //指向文件系统超级块实例
	int mnt_flags;                 //内核内部使用的挂载标记
	struct user_namespace *mnt_userns;
} __randomize_layout;



/*表示一次挂载操作*/
struct mount {
	struct hlist_node mnt_hash;             //散列链表节点成员,将实例链入全局散列表
	struct mount *mnt_parent;               //父mount实例
	struct dentry *mnt_mountpoint;          //挂载点dentry实例指针(跟文件系统目录项)
	struct vfsmount mnt;                    //vfsmount结构体实例,表示在vfs中的挂载信息
	union {
		struct rcu_head mnt_rcu;
		struct llist_node mnt_llist;
	};
#ifdef CONFIG_SMP
	struct mnt_pcp __percpu *mnt_pcp;
#else
	int mnt_count;
	int mnt_writers;
#endif
	struct list_head mnt_mounts;	   // 子mount实例链表头
	struct list_head mnt_child;	     // 链接兄弟mount实例
	struct list_head mnt_instance;	 // 链入超级块中双链表,表头为sb->s_mounts
	const char *mnt_devname;	       // 文件系统所在块设备文件名称,如:/dev/dsk/hda1
	struct list_head mnt_list;       // 将实例链接到挂载命名空间链表
	struct list_head mnt_expire;	   // 用于特定于文件系统的过期链表
	struct list_head mnt_share;	     // 用于共享挂载的循环链表
	struct list_head mnt_slave_list; // 从属挂载链表头
	struct list_head mnt_slave;	     // 用于链入从属挂载链表
	struct mount *mnt_master;	       // 指向包含从属挂载链表头的mount实例
	struct mnt_namespace *mnt_ns;	   // 指向所属挂载命名空间
	struct mountpoint *mnt_mp;	     // 挂载点结构体指针
	union {
		struct hlist_node mnt_mp_list; //将实例添加到挂载点的mount实例链表
		struct hlist_node mnt_umount;
	};
	struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
	__u32 mnt_fsnotify_mask;
#endif
	int mnt_id;			                  // ID标记
	int mnt_group_id;		              // 组ID
	int mnt_expiry_mark;		          // 标记挂载时否过期,true表示过期
	struct hlist_head mnt_pins;
	struct hlist_head mnt_stuck_children;
} __randomize_layout;
/*
mnt_hash: 散列链表节点成员,将实例添加到全局散列表mount_hashtable
mnt_mountpoint:指向挂载点dentry实例
mnt_instance:双链表节点成员,将Mount实例链入超级块的双链表,链表头为sb→s_mounts
mnt_mp:挂载点mountpoint实例
mnt_mp_list:散列链表节点成员,将实例链接到挂载点mountpoint实例的mount实例链表
mnt_list:双链表节点成员,将mount实例链接到挂载命名空间mnt_namespace实例中的双链表
mnt:vfsmount结构体成员,用于建立Mount实例与挂载文件系统的关联
*/




/*表示根文件系统中的挂载点,挂载点对应到跟文件系统中的一个dentry实例,定义了一个mount结构体表示一次挂载操作*/
struct mountpoint {
	struct hlist_node m_hash;    //将mountpoint实例添加到全局散列表mountpoint_hashtable列链表
	struct dentry *m_dentry;     // 指向挂载点 dentry 实例(根文件系统中目录项)
	struct hlist_head m_list;    //挂载点挂载操作链表的头部mount实例
	int m_count;                 //挂载点挂载操作的次数
};


struct path {
  /* 指向vfsmount实例,mount.mnt成员(当前挂载点上挂载的文件系统的挂载信息) */
	struct vfsmount *mnt;
  /*指向挂载点dentry实例(根文件系统中目录项)*/
	struct dentry *dentry;
};


struct mm_struct {
	struct vm_area_struct * mmap;		/* list of VMAs */
	struct rb_root mm_rb;
	struct vm_area_struct * mmap_cache;	/* last find_vma result */
	unsigned long (*get_unmapped_area) (struct file *filp,
				unsigned long addr, unsigned long len,
				unsigned long pgoff, unsigned long flags);
	void (*unmap_area) (struct vm_area_struct *area);
	unsigned long mmap_base;		/* base of mmap area */
	unsigned long free_area_cache;		/* first hole */
	pgd_t * pgd;#!!!此处的pgd是虚拟地址,即调用宏__va()
	atomic_t mm_users;			/* How many users with user space? */
	atomic_t mm_count;			/* How many references to "struct mm_struct" (users count as 1) */
	int map_count;				/* number of VMAs */
	struct rw_semaphore mmap_sem;
	spinlock_t page_table_lock;		/* Protects page tables, mm->rss, mm->anon_rss */

	struct list_head mmlist;		/* List of maybe swapped mm's.  These are globally strung
						 * together off init_mm.mmlist, and are protected
						 * by mmlist_lock
						 */

	unsigned long start_code, end_code, start_data, end_data;
	unsigned long start_brk, brk, start_stack;
	unsigned long arg_start, arg_end, env_start, env_end;
	unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;
	unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;

	unsigned long saved_auxv[42]; /* for /proc/PID/auxv */

	unsigned dumpable:1;
	cpumask_t cpu_vm_mask;

	/* Architecture-specific MM context */
	mm_context_t context;

	/* Token based thrashing protection. */
	unsigned long swap_token_time;
	char recent_pagein;

	/* coredumping support */
	int core_waiters;
	struct completion *core_startup_done, core_done;

	/* aio bits */
	rwlock_t		ioctx_list_lock;
	struct kioctx		*ioctx_list;

	struct kioctx		default_kioctx;

	unsigned long hiwater_rss;	/* High-water RSS usage */
	unsigned long hiwater_vm;	/* High-water virtual memory usage */
};

struct vm_area_struct {
	struct mm_struct * vm_mm;	/* The address space we belong to. */
	unsigned long vm_start;		/* Our start address within vm_mm. */
	unsigned long vm_end;		/* The first byte after our end address
					   within vm_mm. */

	/* linked list of VM areas per task, sorted by address */
	struct vm_area_struct *vm_next;

	pgprot_t vm_page_prot;		/* Access permissions of this VMA. */
	unsigned long vm_flags;		/* Flags, listed below. */

	struct rb_node vm_rb;

	/*
	 * For areas with an address space and backing store,
	 * linkage into the address_space->i_mmap prio tree, or
	 * linkage to the list of like vmas hanging off its node, or
	 * linkage of vma in the address_space->i_mmap_nonlinear list.
	 */
	union {
		struct {
			struct list_head list;
			void *parent;	/* aligns with prio_tree_node parent */
			struct vm_area_struct *head;
		} vm_set;

		struct raw_prio_tree_node prio_tree_node;
	} shared;

	/*
	 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
	 * list, after a COW of one of the file pages.  A MAP_SHARED vma
	 * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
	 * or brk vma (with NULL file) can only be in an anon_vma list.
	 */
	struct list_head anon_vma_node;	/* Serialized by anon_vma->lock */
	struct anon_vma *anon_vma;	/* Serialized by page_table_lock */

	/* Function pointers to deal with this struct. */
	struct vm_operations_struct * vm_ops;

	/* Information about our backing store: */
	unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZE
					   units, *not* PAGE_CACHE_SIZE */
	struct file * vm_file;		/* File we map to (can be NULL). */
	void * vm_private_data;		/* was vm_pte (shared mem) */
	unsigned long vm_truncate_count;/* truncate_count or restart_addr */

#ifndef CONFIG_MMU
	atomic_t vm_usage;		/* refcount (VMAs shared if !MMU) */
#endif
#ifdef CONFIG_NUMA
	struct mempolicy *vm_policy;	/* NUMA policy for the VMA */
#endif
};

struct hd_i_struct {
	int head,sect,cyl,wpcom,lzone,ctl;
	//各字段分别是磁头数、每磁道扇区数、柱面数、写前预补偿柱面号、磁头着陆区柱面号、控制字节。
};

static struct hd_struct {
	long start_sect;
	long nr_sects;
} hd[5*MAX_HD]={{0,0},};

struct request {
	int dev;		/* -1 if no request */
	int cmd;		/* READ or WRITE */
	int errors;
	unsigned long sector;
	unsigned long nr_sectors;
	char * buffer;
	struct task_struct * waiting;
	struct buffer_head * bh;
	struct request * next;
};


struct blk_dev_struct {
	void (*request_fn)(void);
	struct request * current_request;
};

struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
	{ NULL, NULL },		/* no_dev */
	{ NULL, NULL },		/* dev mem */
	{ NULL, NULL },		/* dev fd */
	{ NULL, NULL },		/* dev hd */
	{ NULL, NULL },		/* dev ttyx */
	{ NULL, NULL },		/* dev tty */
	{ NULL, NULL }		/* dev lp */
};

struct inode_operations minix_inode_operations = {
	minix_create,
	minix_lookup,
	minix_link,
	minix_unlink,
	minix_symlink,
	minix_mkdir,
	minix_rmdir,
	minix_mknod,
	minix_rename,
	minix_readlink,
	minix_open,
	minix_release,
	minix_follow_link
};

struct dir_entry {
	unsigned short inode;          //inode节点的编号
	char name[NAME_LEN];           //文件名
};

struct buffer_head {
	char * b_data;			/* pointer to data block (1024 bytes) */
	unsigned long b_blocknr;	/* block number */
	unsigned short b_dev;		/* device (0 = free) */
	unsigned char b_uptodate;
	unsigned char b_dirt;		/* 0-clean,1-dirty */
	unsigned char b_count;		/* users using this block */
	unsigned char b_lock;		/* 0 - ok, 1 -locked */
	struct task_struct * b_wait;
	struct buffer_head * b_prev;
	struct buffer_head * b_next;
	struct buffer_head * b_prev_free;
	struct buffer_head * b_next_free;
};

struct inode {
	dev_t	i_dev;
	ino_t	i_ino;//在磁盘中的inode表排第几个
	umode_t	i_mode;
/*i_mode一共10位,第一位表明结点文件类型,后9位依次为:
i结点所有者、所属组成员、其他成员的权限
(权限有读写执行三种)*/
	nlink_t	i_nlink;
	uid_t	i_uid;
	gid_t	i_gid;
	dev_t	i_rdev;
	off_t	i_size;//文件大小(字节数)
	time_t	i_atime;
	time_t	i_mtime;
	time_t	i_ctime;
	unsigned long i_data[16];
	struct inode_operations * i_op;
	struct super_block * i_sb;
	struct task_struct * i_wait;
	struct task_struct * i_wait2;	/* for pipes */
	unsigned short i_count;//i节点被使用的次数
	unsigned char i_lock;
	unsigned char i_dirt;
	unsigned char i_pipe;
	unsigned char i_mount;
	unsigned char i_seek;
	unsigned char i_update;
};

struct file {
	unsigned short f_mode;
	unsigned short f_flags;
	unsigned short f_count;
	struct inode * f_inode;
	struct file_operations * f_op;
	off_t f_pos;
};

struct super_block {
	unsigned short s_ninodes;
	unsigned short s_nzones;
	unsigned short s_imap_blocks;
	unsigned short s_zmap_blocks;
	unsigned short s_firstdatazone;
	unsigned short s_log_zone_size;
	unsigned long s_max_size;
	unsigned short s_magic;
/* These are only in memory */
	struct buffer_head * s_imap[8];
	struct buffer_head * s_zmap[8];
	unsigned short s_dev;
	struct inode * s_covered;
	struct inode * s_mounted;
	unsigned long s_time;
	struct task_struct * s_wait;
	unsigned char s_lock;
	unsigned char s_rd_only;
	unsigned char s_dirt;
};

struct file_operations {
	int (*lseek) (struct inode *, struct file *, off_t, int);
	int (*read) (struct inode *, struct file *, char *, int);
	int (*write) (struct inode *, struct file *, char *, int);
};

struct inode_operations {
	int (*create) (struct inode *,const char *,int,int,struct inode **);
	int (*lookup) (struct inode *,const char *,int,struct inode **);
	int (*link) (struct inode *,struct inode *,const char *,int);
	int (*unlink) (struct inode *,const char *,int);
	int (*symlink) (struct inode *,const char *,int,const char *);
	int (*mkdir) (struct inode *,const char *,int,int);
	int (*rmdir) (struct inode *,const char *,int);
	int (*mknod) (struct inode *,const char *,int,int,int);
	int (*rename) (struct inode *,const char *,int,struct inode *,const char *,int);
	int (*readlink) (struct inode *,char *,int);
	int (*open) (struct inode *, struct file *);
	void (*release) (struct inode *, struct file *);
	struct inode * (*follow_link) (struct inode *, struct inode *);
};

struct minix_inode {
	unsigned short i_mode;
	unsigned short i_uid;
	unsigned long i_size;
	unsigned long i_time;
	unsigned char i_gid;
	unsigned char i_nlinks;
	unsigned short i_zone[9];
};

struct minix_super_block {
	unsigned short s_ninodes;
	unsigned short s_nzones;
	unsigned short s_imap_blocks;
	unsigned short s_zmap_blocks;
	unsigned short s_firstdatazone;
	unsigned short s_log_zone_size;
	unsigned long s_max_size;
	unsigned short s_magic;
};

struct minix_dir_entry {
	unsigned short inode;
	char name[MINIX_NAME_LEN];
};

你可能感兴趣的:(linux,操作系统)