4.5 linux文件系统-文件目录的操作

1:信息

linux - 0.11
linux/fs/namei.c

2:目的:

文件系统目录的建立,删除,打开,节点建立,删除

  1. 文件系统的文件操作,打开,权限,属主等
  2. 了解文件系统的命令操作 chmod chown mknod
  3. C语言中内存区域的检索和管理,高效代码的xiefa

3: 相关函数

本文件函数主要实现根据目录名或文件名寻找到对应i节点函数namei(),以及一些关于目录的创建和删除,目录项的创建和删除等函数和系统调用

关键结构体:dir_entry

struct dir_entry {
	unsigned short inode; //i节点号
	char name[NAME_LEN]; //名字
};

3.1 权限检测函数permission

struct m_inode {
	unsigned short i_mode; 16位数
	 文件类型 当前用户权限(3位) 用户组权限(3位) 其他用户权限(3位)
	 权限占用i_mode的低9....
}

下面函数用来检查一个文件的读/写/执行权限。
参数:
inode - 文件的i节点指针
mask - 访问属性屏蔽码
返回:访问许可返回1 否则返回0

static int permission(struct m_inode * inode,int mask)
{
	int mode = inode->i_mode; //文件访问属性

/* special case: not even root can read/write a deleted file */
	if (inode->i_dev && !inode->i_nlinks) //i节点有对应设备,但是i节点的链接计数为0 表示文件被删除,则返回
		return 0;
	else if (current->euid==inode->i_uid) //进程的有效用户id == i节点的用户id
		mode >>= 6; //移动6位,是当前用户权限
	else if (current->egid==inode->i_gid)//进程的有效组id == i节点的组id
		mode >>= 3; //移动3位,是用户组权限
	if (((mode & mask & 0007) == mask) || suser())//如果(mode和mash的低3位相等  || 是超级用户) 就返回1
		return 1;
	return 0;
}

3.2 match指定长度字符串比较

成功:返回1
失败:返回0

#define NAME_LEN 14

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])// 如果(长度小于最大长度 并且 字符数组最后一个字符是非0的)就
		return 0;
	//使用嵌入式汇编进行快速语句比较操作。会在用户空间(fs段)执行字符串的比较操作。
	//%0 - eax(比较结果same) %1 - eax(eax的初始值) 2% - esi(名字指针) 3% -edi(目录项目指针) %4-ecx(比较的字节长度值len)
	__asm__("cld\n\t"  //清方向位
		"fs ; repe ; cmpsb\n\t"//用户空间执行循环比较esi++ 和 edi++
		"setz %%al"//如结果一样(zf= 0)则设置al =1(same  = al)
		:"=a" (same)
		:"0" (0),"S" ((long) name),"D" ((long) de->name),"c" (len)
		:"cx","di","si");
	return same; //返回比较结果
}

3.2 find_entry 名字匹配

查找指定目录和文件名的目录项
参数:

  • dir - 指定目录i节点的指针
  • name - 文件名
  • namelen - 文件名长度
  • *res_dir - 接收返回的目录结构指针

该函数在指定目录的数据(文件)中搜索指定的文件名的目录项。
并对指定文件名是".."的情况根据当前的相关设置进行特殊处理

返回值:
成功 - 返回高速缓冲区指针,并在*res_dir返回目录结构的指针。
失败 - 返回NULL

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;//则以最大长度NAME_LEN来截取它
#endif
	entries = (*dir)->i_size / (sizeof (struct dir_entry)); //当前目录i节点中能存放的最大目录个数
	*res_dir = NULL; //置空返回的目录项结构体指针
	if (!namelen) //文件名长度是0 ,返回空
		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)=='.') {//目录名是2 && 第一个字符是“.” && 第二个字符也是"."
/* '..' in a pseudo-root results in a faked '.' (just change namelen) */
//如果当前进程指定的根i节点就是函数参数指定的目录,则说明对于本进程来说,这个目录就是它的伪根目录,
//即进程只能访问该目录的项,而不能回退到其父目录中。也即该进程本目录就是如同文件系统根目录。
//就是说这个目录已经是顶层目录了,没有父目录了
		if ((*dir) == current->root)
			namelen=1; // 因此需要把文件名改成'.'
		else if ((*dir)->i_num == ROOT_INO) { //如果i节点号是1, 说明是文件系统的根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); //取文件系统的超级块
			if (sb->s_imount) {//这个文件系统被安装到的i节点存在 (这个文件系统安装到的节点就是这个文件系统的父目录)
				iput(*dir);//放回原i节点
				(*dir)=sb->s_imount;//dir 指向被安装到的i节点。 对被安装到的i节点进行处理
				(*dir)->i_count++;//对i节点引用计数加1
			}
		}
	}
	//现在开始正常工作,查找name的目录项在什么地方
	if (!(block = (*dir)->i_zone[0]))//先取出第一个块号
		return NULL;
	if (!(bh = bread((*dir)->i_dev,block)))//读取这个块号号缓冲头结构体
		return NULL;
	i = 0; //i是目录中的目录索引号,初始化为0
	de = (struct dir_entry *) bh->b_data;//de指向缓冲块中的数据部分
	while (i < entries) {//在不超过目录中目录项数的条件下,进行循环搜索
	//如果当前目录项数据已经搜索完毕,还没有找到匹配的目录项
		if ((char *)de >= BLOCK_SIZE+bh->b_data) {
			brelse(bh);//则释放当前目录数据块的高速缓冲区
			bh = NULL;
			//再读取目录的下一项对应的逻辑块号 并 读取这个块对于的缓冲区头
			if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||
			    !(bh = bread((*dir)->i_dev,block))) { 
				i += DIR_ENTRIES_PER_BLOCK;
				continue;
			}
			//de再次指向新的数据部分
			de = (struct dir_entry *) bh->b_data;
		}
		if (match(namelen,name,de)) { //如果找到匹配的目录项
			*res_dir = de;//把目录项赋值给*res_dir
			return bh;//返回数据区包含该目录项的高速缓冲区头
		}
		//没有找到的话
		de++;//de指向下一个目录项
		i++;//目录项索引号加1
	}
	//指定的目录dir中所有目录项都搜索完后,还没有找到对应的目录项
	brelse(bh);//则释放目录缓冲区头结构体
	return NULL;//返回NULL
}

3.3 add_entry 添加目录

往指定目录添加一指定文件名的目录项,失败返回NULL

注意:de(指定目录项的结构体指针)的i节点部分被设置为0 -表示在调用该函数的往目录项中提添加信息之间,不能去睡眠。如果睡眠,那么表示其他人(进程)可能会使用了该目录项

根据指定目录和文件名添加目录项
参数:

  • dir - 指定目录的i节点
  • name - 文件名
  • namelen - 文件名长度

返回:高速缓冲区指针;res_dir - 返回目录项结构指针

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])) //取出i节点对应块设备中数据块号(逻辑块)信息,第一块
		return NULL; //如果block为0,说明该目录数据中不含数据,返回空
	if (!(bh = bread(dir->i_dev,block)))//根据设备名和块号,获取对应数据块信息。并放在高速数据缓冲区,并得到指向该高速缓冲区的缓冲区头结构体bh
		return NULL;
	i = 0;//i是当前访问目录项的个数
	de = (struct dir_entry *) bh->b_data;//让数据项结构体de指向指定目录i节点对应的高速缓冲区起始位置。即第一个目录项处
	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;//de指向新的目录项数据块对应的高速缓冲区
		}
		//当前以读的目录项个数的大小 大于等于 该目录i节点信息目录数据长度i_size。
		//说明整个目录文件数据中没有因删除文件留下的空的目录项
		//因此只能添加新目录项到目录文件数据的末端处
		if (i*sizeof(struct dir_entry) >= dir->i_size) {
			de->inode=0; //该目录项的i节点设置为0
			dir->i_size = (i+1)*sizeof(struct dir_entry);//更新该目录文件的长度值(加上一个目录项的长度)
			dir->i_dirt = 1;//设置目录的i节点已修改标志
			dir->i_ctime = CURRENT_TIME;//更新该目录的改变时间为当前时间
		}
		//若目录项的i节点号inode为0,说明找到一个还未使用的的空闲目录项或者是添加的新目录项	
		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;
			bh->b_dirt = 1;//设置含有本目录的相应的高速缓冲区已修改标志
			*res_dir = de;//获取该目录项的指针
			return bh;//返回含有该目录项高速缓冲区头结构体指针
		}
		de++; //目录项已经被使用,则继续检测下一个目录项
		i++;
	}
	函数指向不到这里。也行是linus在写这个代码时,先复制了上面的find_entry,然后修改成本函数的
	brelse(bh);
	return NULL;
}

3.4 get_dir搜索指定目录

该函数根据给出的路径进行路径名搜索,直到到达最顶端的目录

搜索指定路径名的目录(或文件名)的i节点

参数:

  • pathname - 路径名

返回:目录或文件的i节点指针。失败返回NULL

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节点或者当前目录i节点开始
	//因此首先判断进程的根i节点指针和当前工作目录i节点是否有效
	if (!current->root || !current->root->i_count)//如果当前进程没有设备根i节点,或者该进程根i节点指向一个空闲i节点(引用为0)
		panic("No root inode");//系统出错停机
	if (!current->pwd || !current->pwd->i_count)//如进程当前工作目录i节点指针为空,或者当前工作目录指向i节点是一个空闲的i节点
		panic("No cwd inode");//系统出错停机
	if ((c=get_fs_byte(pathname))=='/') {//如果用户指定的路径名第一个字符是“/”,说明路径名时绝对路径名
		inode = current->root;//从根i节点开始操作
		pathname++;
	} else if (c)//第一个字符是其他的字符,说明时相对路径名
		inode = current->pwd;//从进成当前工作目录开始操作
	else//路径名为NULL ,返回NULL
		return NULL;	/* empty name is bad */
	//此时inode指向了正确的i节点 - 进程的根i节点或者当前工作目录i节点之一
	//然后对路径名中的各个目录部分和文件名进行循环出路
	//首先i节点的引用计数增1,表示我们正在使用
	inode->i_count++;
	while (1) {
		thisname = pathname;
		if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) { //i节点有效性判断 和 可执行权限判断
			//如果i节点不是目录节点或者没有进入该目录的许可访问
			iput(inode); //放回i节点
			return NULL;//返回NULL
		}//刚进入循环时当前的i节点就是进程根节点或者当前工作目录的i节点
		
		//每次循环我们处理一个目录名或文件名部分,因此在每次循环中我们都要从路径名字符串中分离一个目录名或文件名
		//方法时从当前路径名指针pathname开始处检测字符,直到结尾是结尾符(NULL)或着是一个‘/’字符
		//此时遍历namelen正好是当前处理目录名部分的长度,变量thisname = pathname正指向该目录名的部分开始处
		for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
			/* nothing */ ;
		if (!c)//如果字符是NULL,说明已经搜索到路径名的结尾,并已经到达指定目录或文件名
			return inode;//返回该i节点
		//在得到当前目录名或者文件名后,调用find_entry来查找目录中的指定名字的目录项
		//如果找到,获取目录项de中,并返回目录项所在的高速缓冲区bh
		if (!(bh = find_entry(&inode,thisname,namelen,&de))) {
		//如果未找到,返回i节点,返回null
			iput(inode);
			return NULL;
		}
	
		inr = de->inode;	//在找到的目录项中取得i节点号
		idev = inode->i_dev;//并目录i节点获取设备号
		brelse(bh);//释放高速缓冲区
		iput(inode);//放回i节点
		if (!(inode = iget(idev,inr)))//根据设备号和节点号,取处对应的i节点。并以该目录项作为当前目录继续循环处理路径名中的下一目录名(或文件名),回到while(1)
			return NULL;
	}
}

3.5 dir_namei返回指定目录的i节点指针,以及最顶层

参数:

  • pathname - 目录名路径
  • namelen - 路径名长度
  • name - 返回最顶层目录名

返回:指定目录名最顶层目录i节点指针和最顶层目录名及长度。出错返回NULL
注意,这里的最顶层目录是路径名最靠近末端的目录

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)))//取得指定路径名最顶层目录i节点
		return NULL;
	basename = pathname;//对路径名pathname进行搜索检测
	while (c=get_fs_byte(pathname++))//查出最后一个‘/’字符后面的名字字符串
		if (c=='/')
			basename=pathname;
	*namelen = pathname-basename-1;//计算其长度
	*name = basename;
	return dir;//返回最顶层目录的i节点指针
}
例如: /home/kayshi/test  name= test  namelen = 4

3.6 namei 取得指定路径名称的i节点

open, link等 则使用他们自己的相应函数。对于chmod这样的命令,使用这个函数就足够了

参数:

  • pathname - 路径名

返回对应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;//不存在就返回NULL
	//最顶层的名字长度是0,则表示路径名是一个目录名作为最后一项,因此我们已经找到对于目录的i节点,直接返回改i节点即可	
	if (!namelen)			/* special case: '/usr/' etc */
		return dir;
	//然后再返回的顶层目录中会找指定文件名目录项的i节点
	//注意,如果最后一个是目录项但是没有加"/",则不会返回最后目录的i节点
	//例如:/usr/src/linux,将只返回src/目录名的i节点。因为dir_name()把不以“/”结束的最后一个名字当作一个文件名来对待
	//所以这需要单独对这种情况使用寻找目录项i节点函数find_entry进行处理
	//此时de中包含要寻找的目录项指针,而dir是包含该目录项的目录的i节点指针
	bh = find_entry(&dir,basename,namelen,&de);
	if (!bh) {
		iput(dir);
		return NULL;
	}
	//取出该目录的i节点号和设备号
	inr = de->inode;
	dev = dir->i_dev;
	brelse(bh);//释放包含该目录项的高速缓冲区
	iput(dir);//放回目录i节点
	dir=iget(dev,inr);//取出最终文件名或目录对应得节点号的i节点
	if (dir) {
		dir->i_atime=CURRENT_TIME;//修改该i节点的访问时间
		dir->i_dirt=1;//修改该i节点的已修改标志
	}
	return dir;//返回该i节点指针
}

3.7 open_namei 完成的打开文件程序

打开时需要对权限进行检测
主要对open函数来创建的

参数

  • pathname - 文件名
  • flag - 打开标志。读写,创建,追加
  • mode - 用于指定文件的许可属性,读写执行。这些属性只应用于将来对文件的调用

返回:成功返回0 失败返回出错码;res _node返回对于文件路径名的i节点指针

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;

	if ((flag & O_TRUNC) && !(flag & O_ACCMODE))//如果(文件截断零标志置位  &&  文件访问模式是只读(0))
		flag |= O_WRONLY;//则在文件打开标志添加只写标志,原因是由于截零标志O_TRUNC必须在文件可写的情况下才生效
		
	mode &= 0777 & ~current->umask;//使用当前进程的文件访问许可屏蔽码,屏蔽掉给定模式中相应的位
	mode |= I_REGULAR;//添上普通文件标志。该标志用于打开的文件不存在而创建文件时,作为新文件的默认属性
	//根据指定路径名寻找到对应的i节点,以及最顶端目录名及长度
	if (!(dir = dir_namei(pathname,&namelen,&basename)))
		return -ENOENT;
	if (!namelen) {		//如果最顶端目录是0	/* special case: '/usr/' etc */
		if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) {//如果 操作不是读写,创建,和文件长度截0,则表示打开的是一个目录名文件操作
			*res_inode=dir;//把该目录的i节点赋值给*res_inode
			return 0;//返回0退出
		}
		//否则说明进程操作非法
		iput(dir);//放回i节点
		return -EISDIR;//返回错误码
	}
	//最顶层文件不是0
	bh = find_entry(&dir,basename,namelen,&de);//根据目录名i节点dir来,查找最顶层路径名字符串对应的目目录结构de,并同时取得该目录项所在的高速缓冲区
	if (!bh) {//缓冲区指针为空,表示没有找到对应文件名的目录项
		if (!(flag & O_CREAT)) {//检测是否是创建操作,如果不是,放回该目录i节点,返回错误号
			iput(dir);
			return -ENOENT;
		}
		//检测是是创建操作
		if (!permission(dir,MAY_WRITE)) {//检测用户有没有写的权限,没有就放回该目录i节点,返回错误号
			iput(dir);
			return -EACCES;
		}
		//检测是是创建操作,并且由写权限
		inode = new_inode(dir->i_dev);//申请一个新的i节点给路径名上指定的文件使用
		if (!inode) {
			iput(dir);
			return -ENOSPC;
		}
		inode->i_uid = current->euid;//设置界定的用户id
		inode->i_mode = mode;//设置节点的模式
		inode->i_dirt = 1;//设置节点的已修改标志位
		bh = add_entry(dir,basename,namelen,&de);//从dir上申请一空闲的目录项de,并获得dir对应的高速缓冲区
		if (!bh) {//获取高速缓冲区指针失败,表示添加目录项操作失败。
			inode->i_nlinks--;//把新节点的应用连接计数减1
			iput(inode);//放回新节点
			iput(dir);//放回目录的i节点
			return -ENOSPC;//返回错误码
		}
		//申请操作成功
		de->inode = inode->i_num;//把de的i节点号设置位新申请节点inode的节点号
		bh->b_dirt = 1;//高速缓冲区的修改标志位置1
		brelse(bh);//释放高速缓冲区
		iput(dir);//放回dir节点
		*res_inode = inode;//把新的节点赋值给res_inode
		return 0;
	}
	//find_entry获得高速缓冲区不空,则说明指定开发的文件已经存在。于是
	inr = de->inode;//取出该目录项的节点号
	dev = dir->i_dev;//取出该目录项的设备号
	brelse(bh);//释放高速缓冲区
	iput(dir);//放回dir节点
	if (flag & O_EXCL)//如果独占标志位O_EXCL置位,但文件已经存在,则返回文件文件已存在出错码退出
		return -EEXIST;
	if (!(inode=iget(dev,inr)))//读取该目录的i节点内容
		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;//更新i节点访问时间为当前时间
	if (flag & O_TRUNC)//如果由截0标志
		truncate(inode);//把该文件长度截为0
	*res_inode = inode;//把目录项节点赋值给*res_inode
	return 0;
}

3.8 sys_mknod创建一个特殊和普通文件节点

属于系统调用,例如 sudo mknod /dev/console && null 在dev下创建console节点和null节点
该函数创建名为filename,由mode和dev指定的文件系统节点(普通文件,设备特殊文件,或命名管道)

参数:

  • filename - 路径名
  • mde - 指定使用许可及所创建节点的类型
  • dev - 设备号

返回:成功返回 - 0 失败返回出错码

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;
	//首先检查操作许可和参数的有效性并取得路径名中顶层目录的i节点
	if (!suser())//如果不是超级用户
		return -EPERM;//返回访问许可出错码
	if (!(dir = dir_namei(filename,&namelen,&basename)))//获取顶层文件名和长度,以及顶层目录的节点
		return -ENOENT;
	if (!namelen) {//顶层文件名长度是0,说明给出的路径名中没有指定的文件名,
		iput(dir);//放回i节点
		return -ENOENT;//返回错误码
	}
	if (!permission(dir,MAY_WRITE)) {//如果该目录中没有写权限
		iput(dir);//放回i节点
		return -EPERM;//返回错误码
	}
	bh = find_entry(&dir,basename,namelen,&de);//从dir中获取指定文件名的目录项结构体de,以及所在的缓冲区
	if (bh) {//得获得缓冲区,对应路径上这个basename的目录项已经存在。就是重复创建
		brelse(bh);//释放缓冲区
		iput(dir);//放回dir节点
		return -EEXIST;//返回已存在的出错码
	}
	//没有获得缓冲区,说明对应路径上这个basename的目录项不存在
	inode = new_inode(dir->i_dev);//根据设备号获得一个新的i节点
	if (!inode) {//失败就放回dir节点,返回错误
		iput(dir);
		return -ENOSPC;
	}
	inode->i_mode = mode;//设置i节点的模式
	if (S_ISBLK(mode) || S_ISCHR(mode))// if (设备是块设备 || 是字符设备)
		inode->i_zone[0] = dev;//让i节点的0号逻辑号直接等于设备号
	inode->i_mtime = inode->i_atime = CURRENT_TIME;//设置i节点的修改时间和访问时间为当前时间
	inode->i_dirt = 1;//节点的修改标志位置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;//目录项所在的高速缓冲区 修改标志位置1
	iput(dir);//放回目录i节点
	iput(inode);//放回新i节点
	brelse(bh);//释放目录i节点的高速缓冲区
	return 0;
}

3.8 sys_mkdir创建文件目录

系统调用 sudo mkdir (0.95版本之前,使用这个命令需要超级用户权限)
与上面sys_mknod函数基本流程一直

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)))
		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);
	if (!inode) {
		iput(dir);
		return -ENOSPC;
	}
	inode->i_size = 32;//目录的节点的大小为32,两个目录项的大小
	inode->i_dirt = 1;
	inode->i_mtime = inode->i_atime = CURRENT_TIME;
	//申请一个block
	if (!(inode->i_zone[0]=new_block(inode->i_dev))) {
		iput(dir);
		inode->i_nlinks--;
		iput(inode);
		return -ENOSPC;
	}
	inode->i_dirt = 1;
	//获取对应block高速缓冲区
	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指向缓冲区头部,第一个dir_entry
	de = (struct dir_entry *) dir_block->b_data;
	//对inode节点和dir_entry进行映射
	
	de->inode=inode->i_num;//第一个dir_entry中节点号是本目录的inode(新申请的)节点号
	strcpy(de->name,".");//第一个dir_entry中名字是 ".",表示本目录
	de++;
	de->inode = dir->i_num;//第二个dir_entry中节点号是上一级目录的节点号
	strcpy(de->name,"..");//第一个dir_entry中名字是 "..",表示上一级的目录
	inode->i_nlinks = 2;//当前inode的连接应用计数为2,(本目录和上次目录)
	dir_block->b_dirt = 1;
	brelse(dir_block);
	inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);
	inode->i_dirt = 1;
	//在上一层目录dir节点中,创建名为basename的dir_entry -> de
	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;
	}
	
	de->inode = inode->i_num;//对目录项结构体de 把新建目录的i节点号赋值给de的结构体号
	bh->b_dirt = 1;//高速缓冲区修改标志位置1
	dir->i_nlinks++;//在上一层目录dir节点的引用计数增1
	dir->i_dirt = 1;//上一层目录dir节点的已修改标志位置1
	iput(dir);//释放dir节点
	iput(inode);//释放新建的inode节点
	brelse(bh);//释放dir节点中,de所在的高速缓冲区
	return 0;
}

3.9 empty_dir检查指定目录是否为空

用于检查指定的目录是否为空的子程序(用于rmdir系统调用)
参数:inode - 指定目录的i节点指针
返回:1 - 目录中是空的 0 -不空

#define BLOCK_SIZE 1024
#define DIR_ENTRIES_PER_BLOCK ((BLOCK_SIZE)/(sizeof (struct dir_entry)))

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);//检查目录中现有目录项的个数
	//检查2个特定的目录项中的信息是否正确,一个目录中最少有2个目录项;即“.”和“..”
	if (len<2 || !inode->i_zone[0] ||
	    !(bh=bread(inode->i_dev,inode->i_zone[0]))) {//如果(目录项个数少于2个 || 第一个直接块没有指向任何磁盘块号 || 该直接块读不出来)
	    	printk("warning - bad directory on dev %04x\n",inode->i_dev);//显示“设备dev上目录错误”
		return 0;//返回0(失败)
	}
	de = (struct dir_entry *) bh->b_data;//de指向高速缓冲区的第一个目录项
	//第一个目录项(“.”)的i节点字段号应该等于当前目录上的节点号
	//第二个目录项(“..”)的i节点字段号应该等于上一层目录上的节点号,不会为0
	//第一个目录项对应的名字字段应该是“.”,第二个目录项对应的名字字段应该是“..”	
	if (de[0].inode != inode->i_num || !de[1].inode || 
	    strcmp(".",de[0].name) || strcmp("..",de[1].name)) {
	    	printk("warning - bad directory on dev %04x\n",inode->i_dev);
		return 0;
	}
	
	nr = 2;//nr是当前目录项号(从0开始)
	de += 2;//de指针递增2,说明指向第三个目录项结构体
	//循环检测该目录中其余的所有的(len-2)个目录项,看看有没有目录项节点i字段不为0(被使用)
	while (nr<len) {
		if ((void *) de >= (void *) (bh->b_data+BLOCK_SIZE)) {//当前de的地址 大于 起始地址加上一整个块的大小,说明该磁盘块中的目录项已经全部检测完毕
			brelse(bh);//释放该磁盘块对应得高速缓冲区
			block=bmap(inode,nr/DIR_ENTRIES_PER_BLOCK);//读取目录数据文件中下一个含有目录项得磁盘块,读取方法是根据当前得目录号nr,计算出对应目录项在目录数据中文件中得数据块号,nr/DIR_ENTRIES_PER_BLOCK
			//通过bmpa来获取对应得盘块号
			if (!block) {
				nr += DIR_ENTRIES_PER_BLOCK;
				continue;
			}
			//通过bread来获取盘块号对应号高速缓冲区
			if (!(bh=bread(inode->i_dev,block)))
				return 0;
			de = (struct dir_entry *) bh->b_data;//de指向新的高速缓冲块首个目录项
		}
		if (de->inode) {//de对应得目录项得节点号inode不为0,表示该目录项当前被使用
			brelse(bh);//释放高速缓冲区
			return 0;//返回0
		}
		de++;//de指向下一个目录项
		nr++;//nr当前已检测目录数加1
	}
	//执行到这里说明在该目录中没有检测到已用得目录项(除了头两个以外),则释放缓冲块,返回1
	brelse(bh);
	return 1;
}

3.10 sys_rmdir删除目录

参数:name - 目录名(路径名)
返回:0 -成功 ,否则返回出错

int sys_rmdir(const char * name)
{
	const char * basename;
	int namelen;
	struct m_inode * dir, * inode;
	struct buffer_head * bh;
	struct dir_entry * de;
//首先检查参数许可和参数得有效性并取得路径名中顶层目录i节点
	if (!suser())//如果不是超级用户,则返回访问许可出错码
		return -EPERM;
	if (!(dir = dir_namei(name,&namelen,&basename)))//获取顶层目录名和长度,以及上一级目录i节点
		return -ENOENT;
	if (!namelen) {//长度为0,路径名最后没有指定目录名
		iput(dir);//放回i节点
		return -ENOENT;//返回出错码
	}
	if (!permission(dir,MAY_WRITE)) {//检查该目录有没有写权限
		iput(dir);
		return -EPERM;
	}
	bh = find_entry(&dir,basename,namelen,&de);//在节点dir中根据basename找到对应对应得目录项结构体de,和这个结构体在得高速缓冲区
	if (!bh) {
		iput(dir);
		return -ENOENT;
	}
	if (!(inode = iget(dir->i_dev, de->inode))) {//根据目录项de中得节点号找到对应节点
		iput(dir);
		brelse(bh);
		return -EPERM;
	}
	//如果(目录中设置了受限删除标志 && 进程有效用户id && 该节点的用户id 不等于 进程有效用户id (表示当前进程没有删除权限))
	if ((dir->i_mode & S_ISVTX) && current->euid &&
	    inode->i_uid != current->euid) {
		iput(dir);//放回要删除目录名的上级节点
		iput(inode);//放回该删除目录的i节点
		brelse(bh);//释放该目录项节点高速缓冲区
		return -EPERM;//放回出错码
	}
	if (inode->i_dev != dir->i_dev || inode->i_count>1) {//if(要删除目录i节点的设备号 != 该目录项上一级目录的设备号  || 引用计数大于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;
	}
	if (!S_ISDIR(inode->i_mode)) {//检查该节点mode是否是一个目录
		iput(inode);
		iput(dir);
		brelse(bh);
		return -ENOTDIR;
	}
	if (!empty_dir(inode)) {//被删除的目录不空,则不能删除
		iput(inode);
		iput(dir);
		brelse(bh);
		return -ENOTEMPTY;
	}
	if (inode->i_nlinks != 2)//一个空目录的链接数应该是2(连接上次目录和本目录),
		printk("empty directory has nlink!=2 (%d)",inode->i_nlinks);//若不等2则显示告警信息,删除可以继续
	de->inode = 0;//目录项结构体中节点号置为0
	bh->b_dirt = 1;//目录项结构体所在的高速缓冲器修改标志位置1
	brelse(bh)//释放高速缓冲区
	inode->i_nlinks=0;//目录节点的链接数置0
	inode->i_dirt=1;//目录节点的修改标志位置1
	dir->i_nlinks--;//上一以目录节点的链接计数减1
	dir->i_ctime = dir->i_mtime = CURRENT_TIME;//修该上一级目录节点的改变时间和修改时间为当前时间
	dir->i_dirt=1;//上一级目录节点修改标志位置1
	iput(dir);//放回上一级节点dir
	iput(inode);//放回被删除目录节点inode
	return 0;
}

3.11 sys_unlink删除文件名对应的目录项

从文件系统删除一个名字。如果是文件的最后一个链接,并且没有进程正在打开该文件,则将该文件删除,并释放所占用的设备空间
参数:name - 文件名
返回:成功返回0,否则返回出错号

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))//获取顶层目录名和长度,和上一级目录节点
		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);//在上一级目录dir节点中找到名字是basename的目录项de,并得到目录项所在的缓冲区高速bh
	if (!bh) {
		iput(dir);
		return -ENOENT;
	}
	if (!(inode = iget(dir->i_dev, de->inode))) {//根据设备号和目录项中的节点号,获得目录对应的节点inode
		iput(dir);
		brelse(bh);
		return -ENOENT;
	}
	//此时我们获得要删除目录项上一级节点dir,要删除目录节点inode和要删除目录项de,下面针对这3个对象中的信息检查来验证删除的可行性
	//if (目录设置了受限删除标志 && 进程的有效用户id不是root && 当前进程的euid  != 该节点的用户id && 当前进程的euid  != 上一级节点的用户id)说明当亲进行没有权限删除该目录
	if ((dir->i_mode & S_ISVTX) && !suser() &&
	    current->euid != inode->i_uid &&
	    current->euid != dir->i_uid) {
		iput(dir);//放回上一级目录节点
		iput(inode);//放回文件名对应i节点
		brelse(bh);//释放目录项所在的高速缓冲区
		return -EPERM;
	}
	if (S_ISDIR(inode->i_mode)) {//文件名是一个目录,则也不能删除
		iput(inode);
		iput(dir);
		brelse(bh);
		return -EPERM;
	}
	if (!inode->i_nlinks) {//链接计数是0,显示错误信息,并修正为1
		printk("Deleting nonexistent file (%04x:%d), %d\n",
			inode->i_dev,inode->i_num,inode->i_nlinks);
		inode->i_nlinks=1;
	}
	//开始删除文件名对应的目录项
	de->inode = 0;//目录项中的i节点号置0
	bh->b_dirt = 1;//包含该目录项的高速缓冲区的修改标志位置1
	brelse(bh);//释放高速缓冲区
	inode->i_nlinks--;//文件名对应i节点的链接计数减1
	inode->i_dirt = 1;;//文件名对应i节点的已修改标志位置1
	inode->i_ctime = CURRENT_TIME;;//文件名对应i节点改变时间修改为当前时间
	iput(inode);;//放回文件名对应i节点
	iput(dir);;//放回文件名对的上一级目录i节点
	return 0;
}

3.12 sys_link为文件建立一个文件名目录项

为一个已存在的文件创建一个新链接(也称硬链接 - hard link)

参数:

  • oldname - 原路径名
  • newname -新路径名
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;
   //对原文件名进行有效性验证,他应该存在并且不是目录名
	oldinode=namei(oldname); //取原文件路径名对应的i节点 oldinode
	if (!oldinode)//0表示出错,就返回错误号
		return -ENOENT;
	if (S_ISDIR(oldinode->i_mode)) {//验证是否是一个目录
		iput(oldinode);//是目录就放回这个节点并返回错误号
		return -EPERM;
	}
	dir = dir_namei(newname,&namelen,&basename);//获取新路径中的顶层目录名和长度,以及上一级的节点号
	if (!dir) {//上一级目录i节点没有找到,放回节点,返回错误
		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);//获得新路径名的目录项de和所在的高速缓冲区bh
	if (bh) {//bh不等空,说明新路径名已经存在,则不能建立连接,释放缓冲区,放回节点,返回错误
		brelse(bh);
		iput(dir);
		iput(oldinode);
		return -EEXIST;
	}
	//bh等于空,说明新路径名不存在
	bh = add_entry(dir,basename,namelen,&de);//则在上一次目录节点dir对应的高速缓冲区bh中添加新的目录项de,并返回bh
	if (!bh) {//bh为空,说明添加失败,放回节点,返回错误
		iput(dir);
		iput(oldinode);
		return -ENOSPC;
	}
	bh不为空,说明添成功,则操作目录项de
	de->inode = oldinode->i_num;//de的节点号原路径的i节点号
	bh->b_dirt = 1;//de所在的高速缓冲区对应的修改标志位置1
	brelse(bh);//释放高速缓冲区
	iput(dir);//放回新文件的上一级目录i节点
	oldinode->i_nlinks++;//原i节点链接计数加1
	oldinode->i_ctime = CURRENT_TIME;//原i节点改变时间修改位当前时间
	oldinode->i_dirt = 1;//原路径i节点的修改标志位置1
	iput(oldinode);//放回原路径i节点
	return 0;//返回0
}

链接文件就是在已存在的文件,添加一个dir_entry

  1. 找到已存在的文件的inode
  2. 在定制的文件路径中创建一个新的dir_entry
  3. 把新的dir_entry映射到老的inode上去
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值