1、文件系统安装含义
1.1 定义
对于文件系统安装,在Ext2文件系统—路径名查找—1 中已有准确的解释,这里做进一步补充说明。所谓安装,就是从一个存储设备上读入超级块,在内存中建立起一个super_block结构,再进而将此设备上的根目录与文件系统中已经存在的一个空白目录挂上钩。安装一个文件系统实际上是安装一个物理设备(文件系统在此物理设备上)。
系统调用mount()将一个可访问的块设备安装到一个可访问的节点上。所谓“可访问”是指该节点或文件已经存在于已安装的文件系统中,可以通过路径名寻访。也就是说,设备安装前就要是“可访问”的,那既然可访问为什么还要安装呢?原因是在安装之前可访问的只是这个设备,如/dev/sda1。这通常是作为一个线性的无结构的字节流来访问的,成为原始设备(rawdevice)。而设备上的文件系统是不可访问的。经过安装以后,设备上的文件系统就成为可访问的了。
1.2 vfsmount
对于vfsmount,在Ext2文件系统—路径名查找—1也有详细解释,这里进一步补充。vfsmount之间的关系如下图
转载自:http://blog.csdn.net/mishifangxiangdefeng/article/details/7566575
(1)vfsmount结构中有mnt_mounts和mnt_child两个队列头,只要上一层vfsmount结构存在,就把当前vfsmount结构中mnt_child链入上一层vfsmount结构的mnt_mounts队列中。这样就形成一颗设备安装的树结构,从一个vfsmount结构的mnt_mounts队列开始,可以找到所有直接或间接安装在这个安装点上的其他设备。
(2)一个安装点可以安装多个设备,一个设备可以安装到多个安装点上。
(3)vfsmount->root指向所安装设备的根目录的dentry,vfsmount->mnt_sb指向所安装设备的超级块的super_block,super_block->s_mounts指向安 装同一设备的vfsmount的队列头。
2、安装
2.1代码分析
-→sys_mount()
参数dev_name为待安装文件系统所在设备的路径名,如果不需要的话就为空(例如,当待安装的是基于网络的文件系统时);dir_name则是安装点(空闲目录)的路径名;type是文件系统的类型,必须是已注册文件系统的字符串名(如“Ext2”,“MSDOS”等);flags是安装模式,如前面所述。Data指向一个与文件系统相关的数据结构(可以为NULL)。
-→-→copy_mount_options()
将字符串形式的或结构形式的参数值从用户空间复制到系统空间。它拷贝整个页面(确切的说是PAGE_SIZE-1个字节),并返回页面的起始地址。
if (!(page = __get_free_page(GFP_KERNEL)))
return-ENOMEM;
/* copy_from_usercannot cross TASK_SIZE ! */
size = TASK_SIZE - (unsigned long)data;
if (size > PAGE_SIZE)
size = PAGE_SIZE;
这里要做的事情是,准备从data开始,拷贝一个page的数据。如果data+PAGE_SIZE超出TASK_SIZE(3GB)的范围(也就是到了内核的空间),那么超出的部分不能拷贝。一般情况下,size > PAGE_SIZE,最终size = PAGE_SIZE,也就是拷贝一个page。用TASK_SIZE来减,是为了避免超出TASK_SIZE。
-→-→-→exact_copy_from_user ()
while (n) {
if (__get_user(c, f)) {
memset(t, 0, n);
break;
}
*t++ = c;
f++;
n--;
}
return n;
一般copy_from_user()函数不会返回出错误时剩下待copy的字节数,但是copy_mount_options()需要这个功能,于是便由exact_copy_from_user实现。exact_copy_from_user是遇到不能访问的内存地址时才返回,返回值是尚未copy的字节数。
返回到copy_mount_options
if (!i) {
free_page(page);
return-EFAULT;
}
if (i != PAGE_SIZE)
memset((char*)page + i, 0, PAGE_SIZE - i);
i表示已copy的字节数。如果一个字节都没被拷贝,i==0,则返回错误。如果拷贝的字节数不足一个page,则把余下的部分填0。
-→-→getname ()
也是将字符串形式的或结构形式的参数值从用户空间复制到系统空间,正常结束时返回内核分配的空间首地址,出错时返回错误代码。但是getname()在复制时遇到字符串结尾符“\0”就停止。
-→-→-→do_getname()
{
int retval;
unsigned long len =PATH_MAX; //内核允许的最大路径长度
//如果进程的地址限制和KERNEL_DS相等,则检查文件名是否小于用户进程空间。
if(!segment_eq(get_fs(), KERNEL_DS)) {
//文件名大于用户进程空间则返回错误
if((unsigned long) filename >= TASK_SIZE)
return-EFAULT;
if(TASK_SIZE - (unsigned long) filename < PATH_MAX)
len= TASK_SIZE - (unsigned long) filename;
}
//将filename拷贝len长度到page中,返回实际拷贝长度。注意这里是用的字符串拷贝,//因此到字符串结尾符“\0”就会停止。
retval =strncpy_from_user(page, filename, len);
-→-→do_mount()
sys_mount的操作主体就是do_mount。
MS_MGC_VAL 和 MS_MGC_MSK是在以前的版本中定义的安装标志和掩码,现在的安装标志中已经不使用这些魔数了,因此,当还有这个魔数时,则丢弃它。
对参数dir_name和dev_name进行基本检查,注意“!dir_name ” 和“!*dir_name”之不同,前者指指向字符串的指针为不为空,而后者指字符串不为空。
-→-→-→memchr()
当第一次遇到字符ch时停止查找。如果成功,返回指向字符ch的指针;否则返回NULL。Memchr()函数在指定长度的字符串中寻找指定的字符,如果字符串中没有结尾符“\0”,也是一种错误。
返回到do_mount()。
将安装标志MS_NOSUID、MS_NOEXEC、MS_NODEV、MS_NOATIME、MS_NODIRATIME、MS_STRICTATIME、MS_RDONLY从flags标志中分离出来,并相应的设置挂载标志变量mnt_flags。
-→-→-→path_lookup ()
这是路径查找函数,已经分析过,不再赘述。这里就是找到挂载点(dir_name)的dentry 结构,并把找到的dentry结构存放在局部变量nd的dentry域中,为后面的挂载所用。
-→-→-→security_sb_mount ()
调用security_sb_mount()函数,来进行安全性检查。security_sb_mount()函数调用security_ops结构的sb_mount成员函数,security_ops是一个静态变量(security/security.c, static struct security_operations*security_ops;),它是一个security_operations 类型的指针。而security_operations类型是一个hook函数表,这些hook函数应用管理与内核对象相关的安全信息以及执行内核每个操作的访问控制。
返回到do_mount()。
它据调用参数 flags 来决定调用以下四个函数之一:do_remount()、 do_loopback()、do_move_mount()、do_new_mount()。
如果flags中的MS_REMOUNT标志位为1,就表示所要求的只是改变一个原已安装设备的安装方式,例如从“只读“安装方式改为“可写”安装方式,这是通过调用do_remount()函数完成的。
如果flags中的MS_BIND标志位为1,就表示把一个“回接”设备捆绑到另一个对象上。回接设备是一种特殊的设备(虚拟设备),而实际上并不是一种真正设备,而是一种机制,这种机制提供了把回接设备回接到某个可访问的常规文件或块设备的手段。通常在/dev目录中有/dev/loop0和/dev/loop1两个回接设备文件。调用do_loopback()来实现回接设备的安装。
如果flags中的MS_MOVE标志位为1,就表示把一个已安装的设备可以移到另一个安装点,这是通过调用do_move_mount()函数来实现的。
而一般的主流情况下,是调用do_new_mount来实现的。
-→-→-→do_new_mount ()
do_new_mount完成新设备的挂载操作。思路很清晰,分两步做,先调用do_kern_mount(传入的参数是dev_name)得到待挂载设备的vfsmount结构,包含了设备的根目录dentry以及设备的super block。然后调用do_add_mount 待挂载设备