linux--文件系统

142 阅读9分钟

文件系统

MINIX文件系统

与标准UNIX文件系统基本相同,6部分组成:引导块、超级块、i节点位图,逻辑块位图、i节点、数据块

文件系统只是在块设备商空出一个存放引导块的空间。如果你把内核映像文件放在文件系统中,那么你就可以在文件系统所在设备的第一个块(即引导块空间)存放实际的引导程序,并由它来取得和加载文件系统中的内核映像文件

对于容量巨大的硬盘块设备,通常会在其上会划分出几个分区,并且在每个分区中都可存放一个不同的完整文件系统

硬盘的第一个扇区是主引导扇区,其中存放着硬盘引导程序和分区表信息。分区表中的信息指明了硬盘上每个分区的类型,在硬盘中起始位置参数和结束位置参数以及占用的扇区总数

超级块

超级块用于存放盘设备上文件系统的结构信息,并说明各部分大小

image.png 在linux0.12系统中,被加载的文件系统超级块保存在超级块表(数组)super_block中,该表共用8项,因此linux-0.12同时最多加载8个文件系统

超级块表将在super.c程序的mount_root()函数中被初始化,在read_super()函数中会为新加载的文件系统在表中设置一个超级块项,并在put_super()函数中释放超级块表中指定的超级块项

逻辑块位图用于描述盘上每个数据盘块的使用情况。除第一个比特位以外,逻辑块位图中每个比特位一次代表盘上数据区中的一个逻辑块

文件中的数据存放在磁盘块的数据区中,而一个文件名则通过对应的i节点与这些数据磁盘块相联系,盘块的号码存放在i节点的逻辑块数组i_zone[]中

打开一个文件时,文件系统会根据给定的文件名找到其i节点,从而通过其对应i节点信息找到文件所在的磁盘块位置

通过用户程序指定的文件名,可以找到对应目录项,根据目录项中的i节点可以找到i节点表中相应的i节点结构。i节点结构中包含着该文件数据的块号信息,因此最终可以得到文件名对应的数据信息

每个i节点结构中都有一个链接计数字段i_nlinks记录着指向该i节点的目录项数,即文件的硬链接计数值。在删除文件时,只有当i节点链接计数值==0内核才会真正删除磁盘上该文件的数据。

由于目录项中i节点仅能用于当前文件系统,因此不能使用一个文件系统的目录项来指向另一个文件系统中的i节点,即硬链接不能跨越文件系统

符号链接类型的文件名目录并不直接指向对应的i节点。符号链接目录项会在对应文件的数据块中存放某一文件的路径名字符串。当访问符号链接目录项时,内核会读取该文件中的内容,然后根据其中的路径名字符串来访问指定的文件。因此符号链接可以不局限在一个文件系统中

struct m_inode {
	unsigned short i_mode;
	unsigned short i_uid;
	unsigned long i_size;
	unsigned long i_mtime;
	unsigned char i_gid;
	unsigned char i_nlinks;
	unsigned short i_zone[9];
/* these are in memory also */
	struct task_struct * i_wait;
	unsigned long i_atime;
	unsigned long i_ctime;
	unsigned short i_dev;
	unsigned short i_num;
	unsigned short i_count;
	unsigned char i_lock;
	unsigned char i_dirt;
	unsigned char i_pipe;
	unsigned char i_mount;
	unsigned char i_seek;
	unsigned char i_update;
};

高速缓冲区

高速缓冲区是文件系统访问块设备中数据的必经要道。为了访问块设备上文件系统中的数据,内核可以每次都访问块设备,或进行读写操作。但是每次IO操作的时间与内存和CPU的处理速度相比是非常慢的

为了提高系统性能,内核就在内存中开辟了一个高速数据缓冲区(buffer cache),并将其划分成一个个与磁盘数据块大小相等的缓冲区块来使用和管理,以减少访问块设备的次数

高速缓冲区存放最近被使用过的各个块设备中的数据块,当需要从块设备中读取数据时,缓冲区管理程序首先会在高速缓冲区寻找。

当需要把数据写到块设备中时,系统会在高速缓冲区中申请一块空闲的缓冲区块来临时存放这些数据。什么时候把数据真正的写到设备中去,是通过设备数据同步实现的

高速缓冲区物理位置:位于内核代码块和主内存区之间。除了块设备驱动程序以外,内核程序如果需要访问块设备中的数据,就都需要经过高速缓冲区来间接的操作

//buffer.c:高速缓冲区管理程序,对硬盘等块设备进行数据高速存取
//bitmap.c: 根据文件系统中逻辑数据块和i节点结构的使用情况,对逻辑块位图和i节点位图分别进行比特位的占用/释放设置操作。逻辑块位图的操作函数是free_block()和new_block()
i节点位图的操作函数是free_inode()和new_inoe()

//block_dev.c :文件系统的数据访问操作,为read_write.c提供服务.块设备文件数据访问操作类程序,块设备读写函数,直接读写块设备上的原始数据
//图 12-26 各种类型文件与文件系统和系统调用的接口函数

linux.png

int block_write(int dev, long* pos, char* buf, int count){
	int block = *pos / BLOCK_SIZE;
	int offset = *pos % BLOCK_SIZE;
	int chars;
	int written=0;
	struct buffer_head *bh;
	register char *p; //局部寄存器变量,被存放在寄存器中
	while(count>0){
		bh = bread(dev, block); //写入缓冲区
		if(!bh){
			return written?written:-EIO;
		}
		chars = (count<BLOCK_SIZE)>count:BLOCK_SIZE;
		p = offset+bh->b_data;
		offfset = 0;
		block++;
		*pos += chars;
		written += chars;
		count -= chars;
		while(chars-- > 0){
			*(p++) = get_fs_byte(buf++);
		}
		bh->b_dirt = 1;
		brelse(bh);
	}
	return written;
}
int block_read(int dev, unsigned long *pos, char* buf, int count){
	int block = *pos / BLOCK_SIZE;
	int offset = *pos % BLOCK_SIZE;
	int chars;
	int read=0;
	struct buffer_head * bh;
	register char *p;
	while(count>0){
		bh = bread(dev, block);
		if(!bh)
			return read?read:-EIO;
		chars = (count<BLOCK_SIZE)?count:BLOCK_SIZE;
		p = offset+bh->b_data;
		
		offset = 0;
		block++;
		*pos += chars;
		read += chars;
		count -= chars;
		while(chars-->0)
			put_fs_byte(*(p++), buf++);
		bh->b_dirt = 1;
		brelse(bh);
	}
	return read;
}
//exec.c 
struct exec {
  unsigned long a_magic;	/* Use macros N_MAGIC, etc for access */
  unsigned a_text;		/* length of text, in bytes */
  unsigned a_data;		/* length of data, in bytes */
  unsigned a_bss;		/* length of uninitialized data area for file, in bytes */
  unsigned a_syms;		/* length of symbol table data in file, in bytes */
  unsigned a_entry;		/* start address */
  unsigned a_trsize;		/* length of relocation info for text, in bytes */
  unsigned a_drsize;		/* length of relocation info for data, in bytes */
};
//加载和执行指定的程序文件,更新当前进程内存空间和相关数据结构
//eip:指向程序计数器的指针,用于更新进程的执行入口点
int do_execve(unsigned long *eip, long tmp, char* filename, char** argv, char **envp){
	struct m_inode *inode;
	struct buffer_head *bh;
	struct exec ex;
	unsigned long page[MAX_ARG_PAGES];
	
	int i, argc, envc;
	unsigned long p;
	if((0xffff & eip[1]) != 0x000f) //检查eip[1]是否符合用户模式调用的要求
		panic("execve called from supervisor mode");
	for(i=0; i<MAX_ARG_PAGES;i++) //clear page table 
		page[i] = 0;
	if(!(inode=namei(filename))) //获取文件inode
		return -ENOENT;
	if(!S_ISREG(inode->i_mode)){
		iput(inode);
		return -EACCES;
	}
	i = inode->i_mode;
	if(current->uid && current->euid){ //检查文件执行权限
		if(current->euid==inode->i_uid)
			i >>= 6;
		else if(current->euid==inode->i_gid)
			i >>=3;
	}else if(i & 0111)
		i =1;
	if(!(i&1)){
		iput(inode);
		return -ENOEXEC;
	}
	if(!(bh=bread(inode->idev, inode->i_zone[0]))){ //读取文件
		iput(inode);
		return -EACCES;
	}
	ex = *((struct exec*)bh->b_data); //读exec-header
	brelse(bh);
	//验证exec文件
	if(N_MAGIC(ex)!=ZMAGIC || ex.a_trsize || ex.a_drsize
		ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
		inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)){
			iput(inode);
			return -ENOEXEC;
	}
	if(N_TXTOFF(ex)!=BLOCK_SIZE) //检查页表
		panic("N_TXTOFF != BLOCK_SIZE. See a.out.h.")
	argc = count(argv);
	rnvc = count(envp);
	//拷贝环境参数和变量
	p = copy_strings(envc, envp, page, PAGE_SIZE*MAX_ARG_PAGES-4);
	p = copy_strings(argc, argv, page,p);
	if(!p){
		for(i=0; i<MAX_ARG_PAGES;i++)
			free_page(page[i]);
		iput(inode);
		return -1;
	}
	//关闭不需要的文件描述符
	for(i=0; i<32; i++)
		current->sig_fn[i] = NULL;
	for(i=0; i<NR_OPEN;i++){
		if((current->close_on_exec>>i)&1)
			sys_close(i);
	}
	current->close_on_exec = 0;
	//释放旧的内存页
	free_page_tables(get_base(current->ldt[1]), get_lomit(0x0f));
	free_page_tables(get_base(current->ldt[2]), get_limit(0x17));
	if(last_task_used_math==current)
		last_task_used_math = NULL;
	current->used_math = 0;
	//加载新程序
	//更改 LDT(局部描述符表),创建新的内存页表,设置新的堆栈和数据段。
	p+=change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
	p = (unsigned long)create_tables((char*)p, argc, envc);
	current->brk = ex.a_bss + (current->end_data+(current->end_code=ex.a_text));
	current->start_stack = p&0xfffff000;
	i = read_area(inode, ex.a_text+ex.a_data);
	iput(inode);
	if(i<0)
		sys_exit(-1);
		
	i = ex.a_text + ex.a_data;
	while(i&0xfff)
		put_fs_byte(0, (char*)(i++));
	//设置程序计数器,设置新的程序入口和栈指针
	eip[0] = ex.a_entry;
	eip[3] = p;
	return 0;
}
//处理局部描述符表(LDT)相关操作的函数,更新进程的 LDT,以便能够正确地访问新的代码段和数据段
//LDT 用于描述进程的段选择子,包括代码段和数据段
//change_ldt 的主要任务是设置新的 LDT 段描述符,并将新的数据段加载到正确的位置。
//这是执行一个新程序时需要的步骤,以确保进程的内存布局能够正确地映射新的代码和数据段。
static unsigned long change_ldt(unsigned long text_size, unsigned long * page){
	unsigned long code_limit, data_limit, code_base, data_base;
	int i;
	
	code_limit = text_size+PAGE_SIZE-1;
	code_limit &= 0xFFFFF000;
	data_limit = 0x4000000;
	code_base = get_base(current->ldt[1]); //获取当前LDT中代码段的基地址
	data_base = code_base;
	
	set_base(current->ldt[1], code_base);
	set_limit(current->ldt[1], code_limit);
	set_base(current->ldt[2], data_base);
	set_limit(current->ldt[2], data_limit);
	
	__asm__("pushl $0x17\n\tpop %%fs"::);
	//映射页面到新的数据段位置
	data_base += data_limit;
	for(i=MAX_ARG_PAGES-1;i>=0;i--){
		data_base -= PAGE_SIZE;
		if(page[i])
			put_page(page[i], data_base);
	}
	return data_limit;
}