Linux内核源码分析--文件系统(七、Namei.c)

目录项:i节点和文件名的联系      

        接下来的学习会遇到目录是怎么存储的,以及之间的一些关系。所以这里先总结下有关目录的关系;

        先来了解下i节点结构:

//内存中i节点结构,前7项和d_inode完全一样
struct m_inode {
	unsigned short i_mode;      //文件类型和属性(rwx位)
	unsigned short i_uid;		//用户id
	unsigned long i_size;		//文件大小
	unsigned long i_mtime;		//修改时间(1970.1.1-- /秒)
	unsigned char i_gid;		//组id
	unsigned char i_nlinks;		//文件目录项链接数
	unsigned short i_zone[9];   //指向逻辑块号
/* these are in memory also */
	struct task_struct * i_wait; //等待该i节点的进程队列
	unsigned long i_atime;  	 //最后访问时间,active
	unsigned long i_ctime;		 //i节点自身修改时间
	unsigned short i_dev;		 //i节点所在设备号
	unsigned short i_num;		 //i节点号
	unsigned short i_count;		 //i节点被使用的次数,0表示空闲
	unsigned char i_lock;		 //是否上锁
	unsigned char i_dirt;		 //已修改标识
	unsigned char i_pipe;		 //管道标志
	unsigned char i_mount;		 //安装标志
	unsigned char i_seek;		 //搜索标志
	unsigned char i_update;		 //更新标志
};
        里面有很多个字段在前面已经学习使用过,这里要说到的是i_zone[9],如果对于目录来说这个数组指定的逻辑号对应的逻辑块里面存放的就是目录项;如果对于文件来说,那么里面存放的就是文件数据了。

        i_size 字段表示这个i节点代表的文件大小,其实就是i_zone[9]数组中使用了多少磁盘;

        知道i节点结构后,再来看看目录项结构:

//文件目录项结构
struct dir_entry {
	unsigned short inode; //该文件名对应的i节点号
	char name[NAME_LEN]; //文件名字符串
};
        这个结构体把文件名和i节点进行了联系,inode是i节点号,name字符数组是文件名;

        下面通过实际例子来说明下这个目录间的关系:在根目录下(/)用命令:ls 显示下  


        会显示出很多的目录来,在底层他们的关系是这样的:根节点/ 有一个目录项dir_entry,其中inode为1,目录名称为/;根据/的dir_entry中的i节点号,可以得到对应的i节点结构,在该i节点结构体中i_zone[i]保存了根目录下的一些文件的目录项(bin  boot  cgroup等等);其他目录下的文件关系也是一样的。

        所以如果你要查找/home/yzh/test/文件,首先是根据文件名/得到i节点号(根目录是特殊的文件目录,由1号i节点专门指定),在该i节点中的i_zone[i]中依次搜索每个目录项;然后让目录项中的名称和yzh字符串进行比较,如果相同,则表示找到了yzh/的目录项了;在yzh目录项中得到yzh目录对应的i节点号,然后在该i节点中继续搜索各个目录项,再让目录项中名称和test进行比较,如果相同,则表示找到了test的目录项了。在查找某个文件时,就是利用这种方法来解决的。

match()

        1、功能和stncmp()函数一样,都是对指定字符串进行比较。比较指定长度len的name和de结构体中的name进行比较,相等返回1,否则返回0;

 //字符串匹配,参数分别为:比较长度,文件名指针,目录项结构体
static int match(int len,const char * name,struct dir_entry * de)
{
	register int same __asm__("ax");//定义寄存器级的变量

//检查范围
	if (!de || !de->inode || len > NAME_LEN)
		return 0;
	if (len < NAME_LEN && de->name[len])//de->name[len]为null表示len大于name的长度
		return 0;
	__asm__("cld\n\t"
		"fs ; repe ; cmpsb\n\t"//循环比较esi++和edi++
		"setz %%al"//如果z=0,则设置al = 1
		:"=a" (same)
		:"0" (0),"S" ((long) name),"D" ((long) de->name),"c" (len)
		:"cx","di","si");
	return same;
}

find_entry()     

        2、static struct buffer_head * find_entry(struct m_inode ** dir, const char * name, int namelen, struct dir_entry ** res_dir)函数,在某个i节点下查找文件名为name的目录项

        功能:在某个目录下搜索所有目录项,查找一个目录项中名称为name的。返回该目录项所在的逻辑块映射的缓存块,在参数中返回查找到的目录项。这是一级目录中查找;在dir节点下的数据块中查找到名称为name的目录项,并且返回该目录项所在的数据块的映射缓存块;参数 res_dir中保持了查找到的目录项;

        参数:dir 在它的结构体中的数据块i_zone[i]中查找目录项; name 需要查找到的目录项中的名称; namelen 需要匹配的目录项中名称的长度; res_dir 查找到的目录项将会存放到这里;

        实现:其实这个函数的实现主要分为两个部分,第一部分是处理文件名为..(父级目录),第二部分是在文件数据块中查找指定文件名的目录项;

        处理文件名为..:1、如果dir节点是当前进程的根节点(在current中会有记录),那么就把..变成.,因为这已经是进程根节点了,说明该进程已经没有权限访问(其实也没有方法)去访问其父节点了;2、如果dir节点是文件系统的根节点,那么就把..指向文件系统的安装节点(方法是:根据dir得到超级块,根据超级块就可以得到安装点);

        查找目录项:根据dir节点结构体中的数据块(i_zone[0]~~i_zone[9])中保存的逻辑号,和dir节点所在的设备,映射得到一个缓存块   ====》  用name在dir数据块中的目录项进行匹配查找,如果查找到了,则把目录项赋值给参数 res_dir;并且返回该目录项所在的数据块(其实就是dir节点中的某个 i_zone[i]的映射缓存块)   ====》  如果第 i 块i_zone[i]中没有找到名称为name的目录项,则查找第 i + 1 块i_zone[i+1]中是否有,直到查找的目录项大于dir节点结构中最大的目录项数,则结束;   

//查找指定目录和文件名的目录项,返回一个含有目录项的映射缓存块
 //参数:dir 指定目录i节点的指针;name 文件名;namelen  文件名长度;
 //res_dir  表示查找到后的目录项
static struct buffer_head * find_entry(struct m_inode ** dir,
	const char * name, int namelen, struct dir_entry ** res_dir)
{
	int entries;
	int block,i;
	struct buffer_head * bh;
	struct dir_entry * de;
	struct super_block * sb;

//对文件名长度的控制
#ifdef NO_TRUNCATE
	if (namelen > NAME_LEN)
		return NULL;
#else
	if (namelen > NAME_LEN)
		namelen = NAME_LEN;
#endif
    //得到指定目录i节点下存放了多少个目录项结构
	entries = (*dir)->i_size / (sizeof (struct dir_entry));
	*res_dir = NULL;
	if (!namelen)//如果需要查找的文件名为空
		return NULL;
	
 //如果是文件名是".."	
/* check for '..', as we might have to do some "magic" for it */
	if (namelen==2 && get_fs_byte(name)=='.' && get_fs_byte(name+1)=='.') {
/* '..' in a pseudo-root results in a faked '.' (just change namelen) */
		if ((*dir) == current->root)//如果当前要查找的目录项i节点就是当前进程的根目录i节点
			namelen=1;//那么..就不能再回退到其父节点了(比如在/目录下cd ..),直接
		else if ((*dir)->i_num == ROOT_INO) {//如果参数目录是根节点
		//则导致目录交换到被安装文件系统的目录i节点上
/* '..' over a mount-point results in 'dir' being exchanged for the mounted
   directory-inode. NOTE! We set mounted, so that we can iput the new dir */
			sb=get_super((*dir)->i_dev);//把..指向于安装文件系统的目录i节点上
			if (sb->s_imount) {
				iput(*dir);
				(*dir)=sb->s_imount;
				(*dir)->i_count++;
			}
		}
	}
	if (!(block = (*dir)->i_zone[0]))//第一个直接数据块,如果为0,则退出
		return NULL;
	if (!(bh = bread((*dir)->i_dev,block)))//把第一个直接块映射到缓存中
		return NULL;
	i = 0;
	//得到参数目录项文件中指定的第一个直接块映射的缓存块
	de = (struct dir_entry *) bh->b_data;
	while (i < entries) {//循环搜索查找的目录项
		if ((char *)de >= BLOCK_SIZE+bh->b_data) {
			brelse(bh);
			bh = NULL;
			//i现在是 1024的倍数+1
			if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||//根据文件数据块号得到设备逻辑块号
			    !(bh = bread((*dir)->i_dev,block))) {//得到映射缓存块
				i += DIR_ENTRIES_PER_BLOCK;//i递增
				continue;
			}
			de = (struct dir_entry *) bh->b_data;//再得到缓存区指针
		}
		if (match(namelen,name,de)) {//比较文件名是否相等,成功返回1
			*res_dir = de;
			return bh;
		}
		de++;
		i++;
	}
	brelse(bh);
	return NULL;
}

add_entry()

        3、static struct buffer_head * add_entry(struct m_inode * dir, const char * name, int namelen, struct dir_entry ** res_dir)函数,就是在dir节点下增加一个新的目录项;

        功能:在dir节点下添加一个新的目录项,方法:在dir节点下的数据块中查找一个空闲的目录项(也即是inode号为0),并且把参数中传递过来的名字复制到该空闲目录项中的name数组中;

        参数:dir 在它的结构体中的数据块i_zone[i]中添加一个新的目录项; name 将要添加的新目录项结构体中的名字数组; namelen 新目录项中名字的长度; res_dir 添加成功后返回的新目录项结构体;

        实现:其实这个函数的实现和find_entry有点类似,首先是在dir节点中的i_zone[i]搜索空闲的目录项(也就是目录项中inode为0的)  ====》  其中分两种情况,一、如果搜索完了dir节点的整个数据块都没有找到一个空闲的目录项,则表示dir节点下的目录项已经满了,只能在该数据块最后依附存储一个目录项;   ====》  如果查找到了一个空闲的目录项,则把参数中的name复制到该目录项中的名字数组中。返回新目录项所在的逻辑块映射的缓存块;

 //参数:dir 指定目录的i节点;name 文件名;namelen 文件名长度
static struct buffer_head * add_entry(struct m_inode * dir,
	const char * name, int namelen, struct dir_entry ** res_dir)
{
	int block,i;
	struct buffer_head * bh;
	struct dir_entry * de;

	*res_dir = NULL;
#ifdef NO_TRUNCATE
	if (namelen > NAME_LEN)
		return NULL;
#else
	if (namelen > NAME_LEN)
		namelen = NAME_LEN;
#endif
	if (!namelen)
		return NULL;
	if (!(block = dir->i_zone[0]))
		return NULL;
	if (!(bh = bread(dir->i_dev,block)))
		return NULL;
	i = 0;
	de = (struct dir_entry *) bh->b_data;
	while (1) {
		if ((char *)de >= BLOCK_SIZE+bh->b_data) {
			brelse(bh);
			bh = NULL;
			block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);
			if (!block)
				return NULL;
			if (!(bh = bread(dir->i_dev,block))) {
				i += DIR_ENTRIES_PER_BLOCK;
				continue;
			}
			de = (struct dir_entry *) bh->b_data;
		}
		//如果该目录中没有空的目录项,那么就放在该目录项数据块后面
		if (i*sizeof(struct dir_entry) >= dir->i_size) {
			de->inode=0;
			dir->i_size = (i+1)*sizeof(struct dir_entry);
			dir->i_dirt = 1;
			dir->i_ctime = CURRENT_TIME;
		}
		//de的i节点为空
		if (!de->inode) {
			dir->i_mtime = CURRENT_TIME;
			for (i=0; i < NAME_LEN ; i++)
				de->name[i]=(i<namelen)?get_fs_byte(name+i):0;//复制文件名到目录项中,不够用0填充
			bh->b_dirt = 1;//表示已经修改过
			*res_dir = de;
			return bh;
		}
		de++;
		i++;
	}
	brelse(bh);
	return NULL;
}

get_dir()

        4、static struct m_inode * get_dir(const char * pathname)函数,返回路径中最后一个目录的(不是文件)i节点

        功能:返回路径名为pathname的最后一级目录的目录项中的i节点;比如:pathname = /home/yzh/test/  则返回test目录项中的i节点;若:pathname = /home/yzh/test  则返回yzh的目录项中的i节点;因为其中判断是以/结尾的目录名;

        参数:pathname  路径名(可以是绝对路径也可以是相对路径)   返回:pathname中以/结尾的最后一级目录项的i节点

        实现:首先是判断第一个字节是否为"/",如果是则表示为绝对路径,inode = current->root;如果不是则表示为相对路径,inode = current->pwd;====》  然后是得到下一个目录(以 / 结尾)thisname,调用find_entry()来寻找inode下目录项中名称为thisname的目录项de;====》  最后根据de中的i节点号和设备号得到thisname目录项的i节点;===》依次循环到pathname最后的NULL,返回最后一级inode

 //参数 路径名;返回路径名中最后一级文件的i节点指针
static struct m_inode * get_dir(const char * pathname)
{
	char c;
	const char * thisname;
	struct m_inode * inode;
	struct buffer_head * bh;
	int namelen,inr,idev;
	struct dir_entry * de;

//进程根i节点,及使用的计数
	if (!current->root || !current->root->i_count)
		panic("No root inode");
//当前工作目录i节点
	if (!current->pwd || !current->pwd->i_count)
		panic("No cwd inode");
	
	//查看判断是绝对路径还是相对路径
	if ((c=get_fs_byte(pathname))=='/') {
		inode = current->root;
		pathname++;
	} else if (c)
		inode = current->pwd;
	else
		return NULL;	/* empty name is bad */
	inode->i_count++;
	while (1) {
		thisname = pathname;
		//是否是目录以及是否有进入权限
		if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {
			iput(inode);
			return NULL;
		}
		for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
			/* nothing */ ;
		if (!c)
			return inode;
		//inode表示在哪个目录下的目录项i节点,thisname是全名,但是会由namelen来截取掉
		if (!(bh = find_entry(&inode,thisname,namelen,&de))) {
			iput(inode);
			return NULL;
		}
		inr = de->inode;
		idev = inode->i_dev;
		brelse(bh);
		iput(inode);
		if (!(inode = iget(idev,inr)))//以下一个目录项为当前目录项继续搜索
			return NULL;
	}
}

dir_namei()

        5、static struct m_inode * dir_namei(const char * pathname,  int * namelen, const char ** name)函数,返回路径中最后一个目录的(不是文件)i节点;得到路径中最后一个目录项名称(目录:返回全为0;文件:返回文件名和长度)

        功能:返回路径名pathname中最后一级目录的i节点指针,以及填充namelen为最后一级目录的字符串长度, name为最后一级目录的名称;比如:pathname  =  /home/yzh/test  ;返回/yzh的i节点指针,namelen 为 test的长度, name 为test;如果 pathname = /home/yzh/test/,则返回/test/的i节点指针,namelen为0,name为NULL;

        参数:pathname  路径名(可以是绝对路径也可以是相对路径) namelen 用来存放最后一级目录的长度, name用来存放最后一级目录的名称(这两个相当于用来返回值的);   返回:pathname中以/结尾的最后一级目录项的i节点指针

        实现:首先根据pathname来得到最后一级以“/”结尾的目录i节点指针 ====》循环得到pathname中的最后一级目录的长度namelen和名称name

 // 返回路径中最后一级文件名的i节点
 // 参数: 路径名;路径名长度;返回的路径名中最后一个文件的名称
 // 其实参数中只有pathname是有用的,namelen和name都是没有使用的
static struct m_inode * dir_namei(const char * pathname,
	int * namelen, const char ** name)
{
	char c;
	const char * basename;
	struct m_inode * dir;

	if (!(dir = get_dir(pathname)))
		return NULL;
	basename = pathname;
	while (c=get_fs_byte(pathname++))
		if (c=='/')
			basename=pathname;//指向最后一级目录
	*namelen = pathname-basename-1;//得到最后一级目录的长度
	*name = basename;//得到最后一级目录名称
	return dir;
}

namei()

        6、struct m_inode * namei(const char * pathname)函数,返回最后一个文件或者目录项的i节点信息

        功能:返回最后一个文件或者目录项的i节点信息;

        参数:pathname  路径名(可以是绝对路径也可以是相对路径) namelen 用来存放最后一级目录的长度, name用来存放最后一级目录的名称(这两个相当于用来返回值的);   返回:pathname中以/结尾的最后一级目录项的i节点指针

        实现:调用dir_namei()得到最后一级目录的目录项i节点,和最后的文件(或者目录)的名称basename和名称长度namelen;====》判断如果路径是目录路径(namelen == 0)则返回得到的dir节点;====》否则表示是文件的路径,调用find_entry()函数,得到文件的目录项和对应的映射块;(如果没有缓存块表示文件不存在,直接退出)====》利用文件目录项得到文件的i节点信息

 //得到指定路径名的i节点
struct m_inode * namei(const char * pathname)
{
	const char * basename;
	int inr,dev,namelen;
	struct m_inode * dir;
	struct buffer_head * bh;
	struct dir_entry * de;
	
//根据路径名得到最后一级目录的文件名及长度,返回对应的i节点
	if (!(dir = dir_namei(pathname,&namelen,&basename)))
		return NULL;
	if (!namelen)			/* special case: '/usr/' etc */
		return dir;//表示该路径名以/结束,则表示找到了该i节点
		//比如 /usr/src/Linux  则返回的是src/的目录项,所以要调用下面函数再次得到Linux的目录项i节点
	bh = find_entry(&dir,basename,namelen,&de);
	if (!bh) {
		iput(dir);
		return NULL;
	}
	inr = de->inode;
	dev = dir->i_dev;
	brelse(bh);
	iput(dir);
	dir=iget(dev,inr);
	if (dir) {
		dir->i_atime=CURRENT_TIME;
		dir->i_dirt=1;
	}
	return dir;
}

open_namei()

        7、int open_namei(const char * pathname, int flag, int mode,  struct m_inode ** res_inode)函数,根据权限和标志打开一个文件

        功能:根据权限和标志打开一个文件

        参数:pathname  路径名(可以是绝对路径也可以是相对路径) namelen 用来存放最后一级目录的长度, name用来存放最后一级目录的名称(这两个相当于用来返回值的);   返回:pathname中以/结尾的最后一级目录项的i节点指针

        实现:首先是判断参数的有效性,屏蔽一些权限 ====》通过dir_namei()函数得到最后一级目录的i节点,以及路径中最后文件(或者目录)的长度和名称;如果namelen为0,表示最后一级为目录,检查下标志位是否有截断和创建(因为这两种标志是对文件操作的);不是,成功返回0;是,出错返回; ====》 如果namelen不为0,则表示最后一级是文件名,然后通过find_entry()得到最后一级文件目录项所在的缓存块,以及目录项结构体;====》如果映射缓存块为空(则就是创建文件操作了),则先剔除掉 不是创建和没有权限两种情况 ====》接下来就是创建文件了:先申请一个i节点,然后在dir节点下添加一个新的目录项,为新目录项设置下就ok了 ====》如果文件开始就存在,则做些设置就可以了。

 //pathname路径名
int open_namei(const char * pathname, int flag, int mode,
	struct m_inode ** res_inode)
{
	const char * basename;
	int inr,dev,namelen;
	struct m_inode * dir, *inode;
	struct buffer_head * bh;
	struct dir_entry * de;
	
    //只读 0   只写 1  读写2
	if ((flag & O_TRUNC) && !(flag & O_ACCMODE))//截断标志置位,且是只读,则加上写权限
		flag |= O_WRONLY;//因为截断标志是需要可写权限
		
	mode &= 0777 & ~current->umask;//屏蔽掉当前进程的屏蔽码
	
	mode |= I_REGULAR;//添加上普通文件标志
	if (!(dir = dir_namei(pathname,&namelen,&basename)))
		return -ENOENT;
	if (!namelen) {			/* special case: '/usr/' etc *///打开一个目录
		if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) {//如果不是截断,创建。。则返回
			*res_inode=dir;
			return 0;
		}//否则报错
		iput(dir);
		return -EISDIR;
	}
	//得到了最后一级目录,且namelen不是0
	bh = find_entry(&dir,basename,namelen,&de);//返回一个含有最后一级目录项的缓存块
	if (!bh) {//找不到映射的缓存块
		if (!(flag & O_CREAT)) {//如果没有创建标志就出错返回
			iput(dir);
			return -ENOENT;
		}
		if (!permission(dir,MAY_WRITE)) {//验证可写权限
			iput(dir);
			return -EACCES;
		}
		inode = new_inode(dir->i_dev);//否则新创建一个目录
		if (!inode) {
			iput(dir);
			return -ENOSPC;
		}//当前进程创建一个文件
		inode->i_uid = current->euid;
		inode->i_mode = mode;
		inode->i_dirt = 1;
		
		//把创建的文件增加到参数上的路径上去
		bh = add_entry(dir,basename,namelen,&de);
		if (!bh) {//如果为null  表示添加失败
			inode->i_nlinks--;
			iput(inode);
			iput(dir);
			return -ENOSPC;
		}
		de->inode = inode->i_num;
		bh->b_dirt = 1;
		brelse(bh);
		iput(dir);
		*res_inode = inode;
		return 0;
	}//下面是如果文件存在的步骤
	inr = de->inode;
	dev = dir->i_dev;
	brelse(bh);
	iput(dir);
	if (flag & O_EXCL)
		return -EEXIST;
	if (!(inode=iget(dev,inr)))
		return -EACCES;
	if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) ||
	    !permission(inode,ACC_MODE(flag))) {
		iput(inode);
		return -EPERM;
	}
	inode->i_atime = CURRENT_TIME;
	if (flag & O_TRUNC)
		truncate(inode);
	*res_inode = inode;
	return 0;
}

sys_mknod()

        8、int sys_mknod(const char * filename, int mode, int dev)函数,根据filename路径名创建一个特殊设备文件或者普通文件节点

        功能:根据filename路径名中最后一个文件来创建一个i节点;filename路径名必须以文件(不是目录)结束,并且该文件要没有对应的目录项。

        参数:filename 路径名;  mode  文件的模式类型; dev  设备号;

        实现:首先是检查参数的有效性 ====》搜索路径名的最后目录的目录项i节点和最后文件的信息(如果最后文件不存在,即:该路径名为一个已知目录的路径名)则退出;====》在最后目录的目录项i节点下搜索文件名,找到则退出(文件名已经存在)====》申请一个新的i节点,如果i节点模式是字符/块设备,则第一个直接块i_zone[0]存放设备号;====》 在最后一个目录的目录项中增加一个新的目录项,并且根据申请的i节点设置该目录项;

//创建一个设备特殊文件或者一个普通文件节点
int sys_mknod(const char * filename, int mode, int dev)
{
	const char * basename;
	int namelen;
	struct m_inode * dir, * inode;
	struct buffer_head * bh;
	struct dir_entry * de;
	
	if (!suser())//非root
		return -EPERM;
	if (!(dir = dir_namei(filename,&namelen,&basename)))//得到最后一级目录的目录项i节点
		return -ENOENT;
	if (!namelen) {//最后一级文件的名称长度
		iput(dir);
		return -ENOENT;
	}
	if (!permission(dir,MAY_WRITE)) {//访问权限检查
		iput(dir);
		return -EPERM;
	}//如果最后一级文件已经存在,则出错退出
	bh = find_entry(&dir,basename,namelen,&de);//获得最后一级目录的目录项所在缓存块和最后一级文件信息
	if (bh) {
		brelse(bh);
		iput(dir);
		return -EEXIST;
	}
	inode = new_inode(dir->i_dev);//申请一个新的i节点
	if (!inode) {
		iput(dir);
		return -ENOSPC;
	}
	inode->i_mode = mode;
	if (S_ISBLK(mode) || S_ISCHR(mode))//如果是字符/块设备则在第一个直接块中保存设备号
		inode->i_zone[0] = dev;
	inode->i_mtime = inode->i_atime = CURRENT_TIME;//设置i节点修改时间
	inode->i_dirt = 1;
	bh = add_entry(dir,basename,namelen,&de);//新增加一个目录项
	if (!bh) {
		iput(dir);
		inode->i_nlinks=0;
		iput(inode);
		return -ENOSPC;
	}//开始设置目录项
	de->inode = inode->i_num;
	bh->b_dirt = 1;
	iput(dir);
	iput(inode);
	brelse(bh);
	return 0;
}

sys_mkdir()

        9、int sys_mkdir(const char * pathname, int mode)函数,根据pathname创建一个新的目录

        功能:根据filename路径名中最后一个文件来创建一个i节点;filename路径名必须以文件(不是目录)结束,并且该文件要没有对应的目录项。

        参数:pathname 路径名(其中包括了要创建的新目录)  mode 去得到权限属性

        实现:首先是检查参数的有效性 ====》搜索路径名的最后目录的目录项i节点和最后文件的信息(如果最后文件不存在,即:该路径名为一个已知目录的路径名)则退出;====》在最后目录的目录项i节点下搜索文件名,找到则退出(文件名已经存在)====》申请一个新的i节点,并且设置该新i节点的目录项(. 和 ..)设置一些属性 ====》 把新目录添加到pathname中开始的最后一级目录的目录项下面,设置目录项中的i节点属性

//创建一个目录
int sys_mkdir(const char * pathname, int mode)//路径名  权限属性
{
	const char * basename;
	int namelen;
	struct m_inode * dir, * inode;
	struct buffer_head * bh, *dir_block;
	struct dir_entry * de;

	if (!suser())
		return -EPERM;
	if (!(dir = dir_namei(pathname,&namelen,&basename)))//得到最后一个目录的目录项i节点
		return -ENOENT;
	if (!namelen) {//如果为0,表示都是已经知道的目录文件
		iput(dir);
		return -ENOENT;
	}
	if (!permission(dir,MAY_WRITE)) {//检查权限属性
		iput(dir);
		return -EPERM;
	}//查找下最后一级文件名其目录项是否存在
	bh = find_entry(&dir,basename,namelen,&de);
	if (bh) {
		brelse(bh);
		iput(dir);
		return -EEXIST;
	}//新申请一个i节点
	inode = new_inode(dir->i_dev);
	if (!inode) {
		iput(dir);
		return -ENOSPC;
	}
	inode->i_size = 32;//目录中默认就有 . 和 .. 两个目录项
	inode->i_dirt = 1;
	inode->i_mtime = inode->i_atime = CURRENT_TIME;
	//申请一个逻辑块用来保存目录项
	if (!(inode->i_zone[0]=new_block(inode->i_dev))) {
		iput(dir);
		inode->i_nlinks--;
		iput(inode);
		return -ENOSPC;
	}
	inode->i_dirt = 1;
	//读取逻辑块中的数据到缓存中
	if (!(dir_block=bread(inode->i_dev,inode->i_zone[0]))) {
		iput(dir);
		free_block(inode->i_dev,inode->i_zone[0]);
		inode->i_nlinks--;
		iput(inode);
		return -ERROR;
	}
	de = (struct dir_entry *) dir_block->b_data;
	//设置第一个目录项.
	de->inode=inode->i_num;//申请的i节点自己的i节点号
	strcpy(de->name,".");
	de++;
	//设置第二个目录项 ..
	de->inode = dir->i_num;//路径名中最后一级目录的i节点号
	strcpy(de->name,"..");
	//设置属性
	inode->i_nlinks = 2;
	dir_block->b_dirt = 1;
	brelse(dir_block);
	inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);
	inode->i_dirt = 1;
	//把新加的目录项添加到路径名最后的目录下
	bh = add_entry(dir,basename,namelen,&de);
	if (!bh) {
		iput(dir);
		free_block(inode->i_dev,inode->i_zone[0]);
		inode->i_nlinks=0;
		iput(inode);
		return -ENOSPC;
	}
	//设置目录项的i节点号为新i节点,设置新i节点的属性
	de->inode = inode->i_num;
	bh->b_dirt = 1;
	dir->i_nlinks++;
	dir->i_dirt = 1;
	iput(dir);
	iput(inode);
	brelse(bh);
	return 0;
}

empty_dir()

        10、static int empty_dir(struct m_inode * inode)函数,判断inode下的目录项是否全部为空(就是判断一个目录是否为空目录)

        功能:检查inode节点下的所有数据块中的目录项,查看是否有非空目录项

        参数:inode  检查该i节点下的所有目录项

        实现:首先检查inode中目录项个数以及直接块的信息;====》 然后检查.和..两个目录项有关的信息 ====》 循环搜索inode下的所有目录项,判断其中是否有非空目录项

//用于检查指定的目录是否为空的
static int empty_dir(struct m_inode * inode)
{
	int nr,block;
	int len;
	struct buffer_head * bh;
	struct dir_entry * de;

	len = inode->i_size / sizeof (struct dir_entry);//得到目录项个数
	if (len<2 || !inode->i_zone[0] ||//如果小于2个目录项,或者没有执行任何磁盘
	    !(bh=bread(inode->i_dev,inode->i_zone[0]))) {//直接块没有映射缓存块
	    	printk("warning - bad directory on dev %04x\n",inode->i_dev);
		return 0;
	}
	de = (struct dir_entry *) bh->b_data;//指向数据缓存区
	if (de[0].inode != inode->i_num || !de[1].inode || //第一个目录项.中的i节点不为当前i节点  上一级目录i节点为0
	    strcmp(".",de[0].name) || strcmp("..",de[1].name)) {//.和..目录项的名称不对
	    	printk("warning - bad directory on dev %04x\n",inode->i_dev);//则任何一个有问题就退出
		return 0;
	}
	nr = 2;//目录项序号
	de += 2;// 指向第三个目录项
	while (nr<len) {//开始搜索全部目录项,查看是否有目录项中i节点不为0的
	//如果第一个磁盘块已经检查完了,那么开始读取第二个磁盘块
		if ((void *) de >= (void *) (bh->b_data+BLOCK_SIZE)) {
			brelse(bh);
			block=bmap(inode,nr/DIR_ENTRIES_PER_BLOCK);//读取下一个逻辑块
			if (!block) {//如果没有逻辑号,就移到下一个逻辑块中
				nr += DIR_ENTRIES_PER_BLOCK;
				continue;
			}
			if (!(bh=bread(inode->i_dev,block)))//把逻辑块数据读取到缓存块中
				return 0;
			de = (struct dir_entry *) bh->b_data;//指向缓存块数据
		}
		if (de->inode) {//表示该目录项正在被使用,非空目录项,返回
			brelse(bh);
			return 0;
		}
		de++;
		nr++;
	}
	brelse(bh);
	return 1;
}

sys_rmdir() 

        11、int sys_rmdir(const char * name)函数,删除name路径名下最后一级的那个目录

        功能:删除name路径名中的最后一级目录,其实name = /home/yzh/test/  是删除不了的;name = /home/yzh/test 如果test是目录才能被删除

        参数:name  路径名(根据这个路径名来查找最后一级目录,然后删除这个目录)

        实现:首先是判断是否为root用户,然后是通过dir_namei()函数获取到最后一级目录的目录项i节点以及最后一级的文件名和长度;====》 通过find_entry()函数获取最后一级文件的目录项;===》得到文件的i节点结构体;====》检查各种属性是否正确,检查文件是否为目录项,是否为空的;====》开始删除inode文件(相当于test),修改dir节点目录项(/yzh)

//删除目录
int sys_rmdir(const char * name)
{
	const char * basename;
	int namelen;
	struct m_inode * dir, * inode;
	struct buffer_head * bh;
	struct dir_entry * de;

	if (!suser())//判断是否为root
		return -EPERM;
	if (!(dir = dir_namei(name,&namelen,&basename)))//读取最后一级目录的目录项i节点
		return -ENOENT;
	if (!namelen) {//如果最后一级文件名不存在,则退出
		iput(dir);
		return -ENOENT;
	}
	if (!permission(dir,MAY_WRITE)) {//检查权限属性
		iput(dir);
		return -EPERM;
	}
	bh = find_entry(&dir,basename,namelen,&de);//查找指定i节点下的指定名称的目录项
	if (!bh) {
		iput(dir);
		return -ENOENT;
	}
	//根据i节点号,得到i节点结构体
	if (!(inode = iget(dir->i_dev, de->inode))) {
		iput(dir);
		brelse(bh);
		return -EPERM;
	}
	//检查删除标志,以及是否是有效用户,该用户有没有删除权限
	if ((dir->i_mode & S_ISVTX) && current->euid &&
	    inode->i_uid != current->euid) {
		iput(dir);
		iput(inode);
		brelse(bh);
		return -EPERM;
	}
	//检查设备号是否相等,以及是否被多次引用
	if (inode->i_dev != dir->i_dev || inode->i_count>1) {
		iput(dir);
		iput(inode);
		brelse(bh);
		return -EPERM;
	}
	//不能删除.目录
	if (inode == dir) {	/* we may not delete ".", but "../dir" is ok */
		iput(inode);
		iput(dir);
		brelse(bh);
		return -EPERM;
	}
	//检查该i节点是否为目录
	if (!S_ISDIR(inode->i_mode)) {
		iput(inode);
		iput(dir);
		brelse(bh);
		return -ENOTDIR;
	}
	//检查该i节点是否为空目录
	if (!empty_dir(inode)) {
		iput(inode);
		iput(dir);
		brelse(bh);
		return -ENOTEMPTY;
	}
	if (inode->i_nlinks != 2)//.和..两个目录项链接
		printk("empty directory has nlink!=2 (%d)",inode->i_nlinks);
	de->inode = 0;//设置目录项i节点为0,表示不用
	bh->b_dirt = 1;//修改缓存块
	brelse(bh);
	inode->i_nlinks=0;//链接数为0
	inode->i_dirt=1;
	//修改父目录的目录项属性
	dir->i_nlinks--;
	dir->i_ctime = dir->i_mtime = CURRENT_TIME;
	dir->i_dirt=1;
	iput(dir);
	iput(inode);
	return 0;
}

sys_unlink()

     12、int sys_unlink(const char * name)函数,删除name路径名下最后一级的那个文件(解引用,如果引用数为0,则删除文件)

        功能:释放name路径名中最后一级文件名,如果引用数为0,则直接删除

        参数:name  路径名(根据这个路径名来查找最后一级文件,然后删除这个文件)

        实现:根据name通过dir_namei()找到最后一级目录,以及文件名称和长度;然后再通过find_endtry()函数查找到最后一级文件的目录项;最后检查下该进程是否有删除权限;最后释放该文件的引用;

//删除/释放 文件名对应的目录项:从文件系统中删除一个名字,
//如果这是文件的最后一个链接,且没有进程打开该文件,则该文件被删除,释放设备空间
int sys_unlink(const char * name)//路径名
{
	const char * basename;
	int namelen;
	struct m_inode * dir, * inode;
	struct buffer_head * bh;
	struct dir_entry * de;

	if (!(dir = dir_namei(name,&namelen,&basename)))//得到最后一级目录的i节点
		return -ENOENT;
	if (!namelen) {//判断最后一级文件名长度
		iput(dir);
		return -ENOENT;
	}
	if (!permission(dir,MAY_WRITE)) {//访问属性检查
		iput(dir);
		return -EPERM;
	}
	//得到目录i节点下的指定名称的目录项
	bh = find_entry(&dir,basename,namelen,&de);
	if (!bh) {
		iput(dir);
		return -ENOENT;
	}
	//得到指定名称的目录项中i节点结构体
	if (!(inode = iget(dir->i_dev, de->inode))) {
		iput(dir);
		brelse(bh);
		return -ENOENT;
	}
	//判断删除属性和用户是否有删除的能力
	if ((dir->i_mode & S_ISVTX) && !suser() &&
	    current->euid != inode->i_uid &&
	    current->euid != dir->i_uid) {
		iput(dir);
		iput(inode);
		brelse(bh);
		return -EPERM;
	}//如果是目录也不能删除
	if (S_ISDIR(inode->i_mode)) {
		iput(inode);
		iput(dir);
		brelse(bh);
		return -EPERM;
	}//如果该文件的链接数已经是0了,则表示出错了
	if (!inode->i_nlinks) {
		printk("Deleting nonexistent file (%04x:%d), %d\n",
			inode->i_dev,inode->i_num,inode->i_nlinks);
		inode->i_nlinks=1;
	}
	//现在开始着手删除工作
	de->inode = 0;//设置目录项为空的
	bh->b_dirt = 1;
	brelse(bh);
	inode->i_nlinks--;//递减链接数
	inode->i_dirt = 1;
	inode->i_ctime = CURRENT_TIME;
	iput(inode);//删除i节点(如果引用数为0,则删除)
	iput(dir);
	return 0;
}

sys_link()

      13、int sys_link(const char * oldname, const char * newname)函数,把oldname指向的文件做个硬链接成新文件

        功能:根据oldname指定的文件,做个硬链接给新的文件newname

        参数:oldname  开始的路径名;newname 新的文件路径名

        实现:首先通过neamei()函数来检查oldname路径名,然后通过dir_namei()和find_entry()来检查newname路径名;如果最后都检查没有问题的话,那么让newname指向oldname路径名上的文件;(其实就是两个不同的目录项,指向同一个i节点)

//为已经存在的文件创建一个新链接(硬链接)
int sys_link(const char * oldname, const char * newname)//都是路径名
{
	struct dir_entry * de;
	struct m_inode * oldinode, * dir;
	struct buffer_head * bh;
	const char * basename;
	int namelen;
    //获取文件名的目录项i节点
	oldinode=namei(oldname);
	if (!oldinode)
		return -ENOENT;
	//判断是否为目录
	if (S_ISDIR(oldinode->i_mode)) {
		iput(oldinode);
		return -EPERM;
	}
	//得到新路径名的最后一级目录的目录项i节点
	dir = dir_namei(newname,&namelen,&basename);
	if (!dir) {
		iput(oldinode);
		return -EACCES;
	}
	//判断新路径名中最后一级是否为文件
	if (!namelen) {
		iput(oldinode);
		iput(dir);
		return -EPERM;
	}
	//硬链接不能跨设备
	if (dir->i_dev != oldinode->i_dev) {
		iput(dir);
		iput(oldinode);
		return -EXDEV;
	}
	//如果用户在新路径中没有写权限,也退出
	if (!permission(dir,MAY_WRITE)) {
		iput(dir);
		iput(oldinode);
		return -EACCES;
	}
	//查找下指定文件名文件名的目录项是否已经存在
	bh = find_entry(&dir,basename,namelen,&de);
	if (bh) {
		brelse(bh);
		iput(dir);
		iput(oldinode);
		return -EEXIST;
	}
	//在指定目录下添加一个新的目录项
	bh = add_entry(dir,basename,namelen,&de);
	if (!bh) {
		iput(dir);
		iput(oldinode);
		return -ENOSPC;
	}
	//让该目录项指向老的路径名文件中的文件目录项信息
	de->inode = oldinode->i_num;
	bh->b_dirt = 1;
	brelse(bh);
	iput(dir);
	//修改下文件的i节点的属性值
	oldinode->i_nlinks++;
	oldinode->i_ctime = CURRENT_TIME;
	oldinode->i_dirt = 1;
	iput(oldinode);
	return 0;
}

        转载请注明作者和原文出处,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/44060399

        若有不正确之处,望大家指正,共同学习!谢谢!!!


你可能感兴趣的:(操作系统,文件系统,高速缓存,linux内核源码)