VFS部分是0.95的代码,内存部分是2.6.11领略四级页表和虚存,其余是0.11
下面是磁盘的内容,其中i节点就是一个inode数组,逻辑块就是数据块可用于存放数据
操作系统通过将磁盘数据读入到内存中指定的缓冲区块来与磁盘交互,对内存中的缓冲区块修改后写回磁盘。
进程(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中完成这些连接
除此以外,还有一个哈希链表数组hash_table(一开始空,当某个缓冲区被用到了才会加入到该数组),根据设备号和块号哈希映射到数组的某个索引,冲突则通过链表连接起来(buffer_head的b_prev和b_next)
暂时发现只有在读写缓冲区的时候缓冲区才会被上锁,
uptodate针对读,为1的时候表示缓冲区内容和磁盘中的一致;dirt针对写,为1的时候表示缓冲区被更新了,需要同步到磁盘
inode的i_count:
file的f_count:
只有当具体某个文件被读或写时,其inode才会被加载到内存的inode缓存表中,即inode_table[NR_INODE],每个inode元素都被初始化为0了,相当于提前先生成inode对象,使得内存中常驻NR_INODE个inode可被使用,而不必临时new一个,只需要直接初始化空闲inode的每个属性,这会快很多
除了少数条件会自动触发自动同步之外,码农需要手动调用sys_sync系统调用使得刚刚写的文件会立刻同步到磁盘上,否则你得等。linux0.11做的仅仅是标记该缓冲区为脏。在2.6版本,linux会专门有个pdflush线程周期性地检查是否有脏缓冲区并且自动同步到磁盘
这样设计是可理解的,没有空闲缓冲区的时候,申请该资源的进程没有可以加入的队列(不知道加入到哪个缓冲区),现在专门用一个队列来让他们排队,同时也能阻塞他们了
#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:暂停状态.处于该状态的进程通过其他进程的信号才能被唤醒
导致缺页异常的虚拟地址根本不在进程的“虚存区间”中,段错误。(栈扩展是一种例外情况)
地址在“虚存区间”中,但“虚存区间”的访问权限不够;例如“区间”是只读的,而你想写,段错误
映射关系没建立
映射关系也建立了,但是页面不在内存中。肯定是换出到交换分区中了,换进来再说
页面也在内存中。但页面的访问权限不够。例如页面是只读的,而你想写。这通常就是 “写时拷贝COW” 的情况。
缺页异常发生在“内核动态映射空间”。这是由于进程进入内核后,访问一个通过 vmalloc() 获得线性地址而引起的异常。对这种情况,需要将内核页目录表、页表中对应的映射关系拷贝到进程的页目录表和页表中。
作用:将当前执行该函数的进程即CURRENT插入到等待队列的队首并阻塞,其中p是等待某个资源的队列的队首的pcb的指针的指针
实现:
作用:将当前进程插入到等待进程队列的队首指针p(头插),并将队首指针指向当前进程
实现:
作用:将传入的进程p唤醒
实现:
返回值:int
作用:
实现:
超级块全局数组super_block super_block[NR_SUPER]
设备号为0==空闲超级块槽
返回值: super_block *
作用:从超级块全局数组中获取设备号对应设备的超级块
实现:遍历超级块全局数组,直到当前被遍历超级块的设备号与dev相等
作用:释放(即清空、初始化)超级块全局数组中设备号所对应设备的超级块
实现:
返回值:super_block *
作用:找到超级块全局数组空槽并从该设备读取超级块到空槽中
实现:
返回值:int
作用:根据设备名(即dev_name,准确说是全路径名)卸载指定设备。注意!对于设备文件,其inode的i_zone[0]是设备号
实现:
返回值:inode *
作用:根据全路径名获取到该文件的inode
实现:
返回值: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)宏代表表达式的最终值。*/
作用:开始加载文件系统
实现:
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 *
作用:从设备号为dev的设备上读取第nr个inode
实现:
返回值:int
作用:根据inode的i_data得到该文件逻辑块号为block的全局物理块号。create=1时,如果逻辑块block还未被分配全局物理块号,则按照分配算法分配给该逻辑块,将该逻辑块映射到物理块。create=0时即使未被分配也不管,直接返回初始值。
实现:
返回值:inode *
作用:从inode缓存表中找到空闲的inode节点并初始化
实现:
返回值:int
作用:在minix文件系统下创建设备文件。
实现:
返回值:buffer_head *
作用:在minix文件系统下,只把name加入目录文件dir的目录即minix_dir_entry数组,inode统一被初始化为0
实现:
返回值:inode *
作用:获取pathname中最底层目录的inode,并且用户传入的name会被赋值为最底层的目录名或者文件名(如/a/b/c得到c)
实现:
返回值:inode *
作用:获取目录pathname最底层目录的inode。比如/var/log/httpd,将只返回 log/目录的inode,/var/log/httpd/则返回httpd/的目录
实现:
返回值:buffer_head *
作用:根据目录dir的inode找到其下名为name的目录项。其中res_dir存放该目录项,而返回值是该目录项所在高速缓冲区的头部。
实现:
返回值:int
作用:可以基于任意文件系统创建设备文件(体现VFS)。与sys_creat(创建普通文件,底层调用的是sys_open)不同在于sys_mknod可以输入设备号参数即dev。
实现:
返回值:int
作用:从设备号为dev的设备中找到空闲的数据块并返回该块在整个设备的块号
实现:
返回值:inode *
作用:在minix文件系统下,在磁盘找到空闲的inode并初始化
实现:
返回值:inode *
作用:将传入的minix文件系统的inode标记为脏
实现:
返回值:int
作用:在inode中获取第block个逻辑块的物理块,如果发现该逻辑块还没有被分配物理块,则通过遍历数据块位图找到空闲的物理块分配给该逻辑块
实现:
作用:当前进程释放对缓冲区的引用
实现:
作用:令当前进程等待缓冲区资源。如果缓冲区无人占用则无需等待,否则将当前进程加入到该资源的等待队列并阻塞
实现:
返回值:buffer_head *
作用:将设备号为dev的设备中的第block块数据读入到缓冲区并返回该缓冲区
实现:
定义:BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
返回值:char(但是数字)
作用:分配空闲缓冲区时衡量缓冲区的好坏(空闲)程度。同样没上锁的两个缓冲区,被修改过的那个是更坏的缓冲区(2)。同样没被修改过的两个缓冲区,上锁的那个是更坏的缓冲区(1)。既被上锁还被修改过则是最坏的缓冲区(3),既没被上锁也没被修改过则是最好的缓冲区(0)(b_dirt和b_lock的值只会为0或1)。
返回值:buffer_head *
作用:返回装有设备号为dev的设备中块号为block的内容的缓冲区
实现:
作用:根据读写标记rw在缓冲区和块设备之间进行读或写(一个缓冲区的内容)
实现:
作用:从全局request数组找到空闲项然后创建设备读写请求项并插入设备请求队列
实现:
作用:使用C-SCAN电梯算法将当前请求req请求插入到请求队列
实现:
返回值:int
作用:
实现:
1.
2.
作用:系统初始化时初始化硬盘相关的内容
实现:
作用:处理硬盘读写请求队列中的当前(首个)请求
实现:
假设先使用完里面的柱面,再使用外面的。
每个盘面都分别有磁头
如上图,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函数处理
作用:每写完一个扇区后的中断处理函数(确切地说是中断处理函数调用到了这个函数,磁盘中断发生并不是直接调用该函数的,而是通过一些分支间接调用到了)
实现:
作用:每读完一个扇区后的中断处理函数,具体地说,是发送读命令给磁盘控制器后,磁盘会将一个扇区的数据送到它自己的缓冲区,并发起一次中断然后调用到read_intr
实现:
返回值:int
作用:
实现:
返回值:int
作用:处理缺页中断
实现:
1.根据给定虚拟地址addr(计算完分段后的地址)查找满足如下条件之一的VMA(VMA同时存在两种组织形式,双向链表和红黑树,在这里是通过红黑树来查找的即O(logN)),如下图
即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)处理各级页表未建立映射关系以及页面在交换分区导致的缺页中断
返回值:int
作用:处理各级页表未建立映射关系以及页面在交换分区导致的缺页中断。此处应该是四级(个)页表,五次映射运算。注意,每个页表的项放的都是物理地址
实现:
因为程序都是虚拟地址,都要进行页表映射才能找到物理地址,要想修改页表则程序中仍需要用虚拟地址。所有页表都存放在物理内存的内核空间中,
返回值:int
作用:
实现:
返回值:int
作用:为读写某个虚拟地址而其未被分配对应物理页而产生的缺页中断分配物理页
实现:
返回值:long
作用:挂载外部存储设备的文件系统到根文件系统中,
mount -t ext4 /dev/cdrom /mnt将文件系统已被格式化为ext4的设备/dev/cdrom挂载到/mnt目录下
(通常挂载之前都要先格式化外部存储设备的文件系统为当前os支持的文件系统,即命令mkfs -t ext4 /dev/sdb1)
实现:
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;
}
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;
}
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;
}
(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()函数创建的数据结构实例组织关系如下图所示:
(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实例的创建
我们来看下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主要的工作,如下:
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()函数创建的数据结构实例及组织关系如下图所示:
执行内核的挂载函数vfs_kern_mount:该函数主要是创建文件系统超级块super_block、根目录项dentry和inode结构体实例,并创建表示本次挂载操作的mount结构体实例,mount实例添加到超级块实例s_mounts成员链表中,并与挂载文件系统根目录项dentry建立关联
关联挂载点do_add_mount:创建挂载点mountpoint结构体实例,并添加到全局散列表,mountpoint实例关联到挂载点dentry实例(跟文件系统中目录项),并将挂载mount实例添加到Mountpoint实例链表和全局散列表中,建立mount实例与挂载断点dentry之间的关联,一个挂载点可以有多个挂载,因此Mountpoint实例包含一个挂载mount实例的链表
执行完这两步,通过mount实例建立了挂载点dentry实例和挂载文件系统根目录项dentry实例之间的联系。
当内核打开文件搜索路径到达挂载点时(挂载点dentry实例设置DCACHE_MOUNTED标记位),将调用函数lookup_mnt(path),在mount实例全局散列表中查找第一个关联到挂载点dentry实例的mount实例,搜索路径随后进入mount实例关联的挂载文件系统根目录项。
返回值:
作用:
实现:
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];
};