14.14任务的工作目录
在 Linux 中咱们经常会使用命令 pwd
来显示当前工作目录,还要用 cd
命令来改变工作目录,咱们要实现类似的功能。
14.14.1 显示当前工作目录的原理及基础代码
Linux中使用pwd
来显示当前工作路径。任何目录中都有目录项“…”,它表示父目录。也就是说,有了“…”,无论我们身处任何一级的子目录,都可以“顺藤摸瓜”找到根目录。因此咱们具体的做法是先通过“…”获取当前目录的父目录,在父目录中搜索当前目录的目录项,从目录项中获取当前目录名称,然后再向上找父目录的父目录,再从中获得父目录的名称……沿着目录树层层而上,就能构建出当前目录的绝对路径。
/*获得父目录的inode编号*/
static uint32_t get_parent_dir_inode_nr(uint32_t child_inode_nr, void* io_buf){
struct inode* child_dir_inode = inode_open(cur_part,child_inode_nr);
/*目录中的目录项"..",中包含了父目录inode编号,".."位于目录的第0块*/
uint32_t block_lba = child_dir_inode->i_sectors[0];
ASSERT(block_lba>=cur_part->sb->data_start_lba);
inode_close(child_dir_inode);
ide_read(cur_part->my_disk,block_lba,io_buf,1);
struct dir_entry* dir_e = (struct dir_entry*)io_buf;
/*第0个目录项是".",第1个目录项是".."*/
ASSERT(dir_e[1].i_no < 4096 && dir_e[1].f_type == FT_DIRECTORY);
return dir_e[1].i_no; //返回..即父目录的inode编号
}
/*在inode编号为p_inode_nr的目录中查找inode编号为c_inode_nr的子目录的名字,将名字存入缓冲区path,成功返回0,失败返回-1*/
static int get_child_dir_name(uint32_t p_inode_nr, uint32_t c_inode_nr, char* path, void* io_buf){
struct inode* parent_dir_inode = inode_open(cur_part,p_inode_nr);
/*填充all_blocks,将该目录的所占扇区地址全部写入all_blocks*/
uint8_t block_idx = 0;
uint32_t all_blocks[140] = {0}, block_cnt = 12;
while(block_idx < 12){
all_blocks[block_idx] = parent_dir_inode->i_sectors[block_idx];
block_idx++:
}
if(parent_dir_inode->i_sectors[12]){ //若包含了一级间接块表,将其读入 all_blocks
ide_read(cur_part->my_disk,parent_dir_inode->i_sectors[12],all_blocks+12,1);
block_cnt = 140;
}
inode_close(parent_dir_inode);
struct dir_entry* dir_e = (struct dir_entry*)io_buf;
uint32_t dir_entry_size = cur_part->sb->dir_entry_size;
uint32_t dir_entrys_per_sec = (512 / dir_entry_size);
block_idx=0;
/*遍历所有块*/
while(block_idx<block_cnt){
if(all_blocks[block_idx]){ //如果相应块不为空,则读入相应块
ide_read(cur_part->my_disk,all_blocks[block_idx],io_buf,1);
uint8_t dir_e_idx = 0;
/*遍历每个目录项*/
while(dir_e_idx < dir_entrys_per_sec){
if((dir_e+dir_e_idx)->i_no == c_inode_nr){
strcat(path,"/");
strcat(path,(dir_e+dir_e_idx)->filename);
return 0;
}
dir_e_idx++;
}
}
block_idx++;
}
return -1;
}
get_parent_dir_inode_nr
:接受 2 个参数,子目录 inode
编号 child_inode_nr
、缓冲区 io_buf
,功能是获得父目录的 inode
编号。
get_child_dir_name
:接受 4 个参数,父目录 inode
编号 p_inode_nr
、子目录 inode
编号 c_inode_nr
、存储路径的缓冲区 path
、硬盘读写缓冲区 io_buf
,功能是在 inode 编号
为p_inode_nr
的目录中查找 inode
编号为 c_inode_nr
的子目录,将子目录的名字存入缓冲区 path
,成功返回 0,失败返-1。
14.14.2 实现sys_getcwd
Linux 中用 getcwd
函数来获取当前工作路径,其原型是:
char *getcwd(char *buf, size_t size)
buf
是容纳当前目录绝对路径的缓冲区, getcwd
会将当前工作目录的绝对路径写入 buf
中, size
是 buf
的大小。 buf
可以由用户提供,也可以由操作系统提供,如果用户不提供 buf
,即传给 buf
的参数为 NULL,系统会通过 malloc
单独分配内存给 buf,
之后 getcwd
再将 buf
返回,用户进程记得要用free
释放。本节咱们要实现其内核部分-sys_getcwd
.
/*把当前工作目录绝对路径写入buf,size是buf的大小,当buf为NULL时,由操作系统分配存储工作路径的空间并返回地址,失败返回NULL*/
char* sys_getcwd(char* buf, uint32_t size){
/*确保buf不为空,若用户进程提供的buf为NULL,系统调用getcwd中要为用户进程通过malloc分配内存*/
ASSERT(buf!=NULL);
void* io_buf = sys_malloc(SECTOR_SIZE);
if(io_buf==NULL){
return NULL;
}
struct task_struct* cur_thread = running_thread();
int32_t parent_inode_nr = 0;
int32_t child_inode_nr = cur_thread.cwd_inode_nr; //这个cwd_inode_nr是用来记录工作目录的inode编号。
ASSERT(child_inode_nr >= 0 && child_inode_nr < 4096); //最大支持4096个inode
/*若当前目录是根目录,直接返回'\'*/
if(child_inode_nr == 0){
buf[0]='/';
buf[1]= 0;
return buf;
}
memset(buf,0,size);
char full_path_reverse[MAX_PATH_LEN] = {0}; //用来做全路径缓冲区
/**
* 从下往上逐层找父目录,直到找到根目录为止。
* 当child_inode_nr为根目录的inode编号(0)时停止
* 即已经查看完根目录中的目录项
*/
while((child_inode_nr)){
parent_inode_nr = get_parent_dir_inode_nr(child_inode_nr,io_buf);
if(get_child_dir_name(parent_inode_nr,child_inode_nr,full_path_reverse,io_buf)==-1){ //或未找到名字,失败退出
sys_free(io_buf);
return NULL;
}
child_inode_nr = parent_inode_nr;
}
ASSERT(strlen(full_path_reverse)<=size);
/**
* 至此full_path_reverse中的路径是反着的
* 即子目录在前,父目录在后,
* 现在将full_path_reverse中的路径反置
*/
char* last_slash; //用于记录字符串中最后一个斜杠地址
while((last_slash == strrchr(full_path_reverse,'/'))){
uint16_t len = strlen(buf);
strcpy(buf+len, last_slash);
/*full_path_reverse中添加结束字符,作为下一次执行strcpy中last_slash的边界*/
*last_slash = 0;
}
sys_free(io_buf);
return buf;
}
sys_getcwd
:把当前工作目录绝对路径写入buf,size是buf的大小,当buf为NULL时,由操作系统分配存储工作路径的空间并返回地址,失败返回NULL。
原理:我们之前已经为PCB中添加了cwd_inode_nr
用于表示任务工作目录inode
索引,假设现在这个inode
索引是正确的。首先调用get_parent_dir_inode_nr
得到父目录inode
索引,此时原来的cwd_inode_nr
就变成了子目录inode
索引,然后就可以调用get_child_dir_name
得到子目录名称,然后将父目录inode
索引转换成新的子目录索引,又调用get_parent_dir_inode_nr
得到新的父目录索引…如此循环,缓冲区中就会存储反转的绝对路径。比如一个进程工作在/home/kanshan/test
下,缓冲区就会存入/test/kanshan/home
,所以我们最后把这个路径反转过来即可。
14.14.3 实现sys_chdir改变工作目录
Linux中用函数chdir
来改变当前工作目录,其原型是:int chdir(const char *path)
;
/*更改当前工作目录为绝对路径path,成功则返回0,失败返回-1*/
int32_t sys_chdir(const char* path){
int32_t ret=-1;
struct path_search_record searched_record;
memset(&searched_record,0,sizeof(struct path_search_record));
int inode_no = search_file(path,&searched_record);
if(inode_no!=-1){
if(searched_record.file_type == FT_DIRECTORY){
running_thread()->cwd_inode_nr = inode_no;
ret = 0;
}else{
printk("sys_chdir:%s is regular file or other!\n", path);
}
}
dir_close(searched_record.parent_dir);
return ret;
}
sys_chdir
:更改当前工作目录为绝对路径path,成功则返回0,失败返回-1。任务的工作目录记录在 pcb
中的 cwd_inode_nr
,因此更改工作目录的核心原理就是修改 cwd_inode_nr
。