操作系统真象还原实验记录之实验二十七:文件的打开、关闭、写、读、读写指针定位

操作系统真象还原实验记录之实验二十七:文件的打开、关闭、写、读、读写指针定位

1. 文件的打开与关闭

fs.c之sys_open完善

switch (flags & O_CREAT) {
      case O_CREAT:
	 printk("creating file\n");
	 fd = file_create(searched_record.parent_dir, (strrchr(pathname, '/') + 1), flags);
	 dir_close(searched_record.parent_dir);
	 break;
      default:
   /* 其余情况均为打开已存在文件:
    * O_RDONLY,O_WRONLY,O_RDWR */
	 fd = file_open(inode_no, flags);
}

增加default,处理只读、只写、读写情况。
均要先打开文件

file.c之file_open


/* 打开编号为inode_no的inode对应的文件,若成功则返回文件描述符,否则返回-1 */
int32_t file_open(uint32_t inode_no, uint8_t flag) {
   int fd_idx = get_free_slot_in_global();
   if (fd_idx == -1) {
      printk("exceed max open files\n");
      return -1;
   }
   file_table[fd_idx].fd_inode = inode_open(cur_part, inode_no);
   file_table[fd_idx].fd_pos = 0;	     // 每次打开文件,要将fd_pos还原为0,即让文件内的指针指向开头
   file_table[fd_idx].fd_flag = flag;
   bool* write_deny = &file_table[fd_idx].fd_inode->write_deny; 

   if (flag == O_WRONLY || flag == O_RDWR) {	// 只要是关于写文件,判断是否有其它进程正写此文件
						// 若是读文件,不考虑write_deny
   /* 以下进入临界区前先关中断 */
      enum intr_status old_status = intr_disable();
      if (!(*write_deny)) {    // 若当前没有其它进程写该文件,将其占用.
	 *write_deny = true;   // 置为true,避免多个进程同时写此文件
	 intr_set_status(old_status);	  // 恢复中断
      } else {		// 直接失败返回
	 intr_set_status(old_status);
	 printk("file can`t be write now, try again later\n");
	 return -1;
      }
   }  // 若是读文件或创建文件,不用理会write_deny,保持默认
   return pcb_fd_install(fd_idx);
}

和create一样先需要一个全局打开表空位和一个inode。
填好该打开表表项,inode_open会填好或者找出inode。
如果是只写或者读写,
就要判断inode->write_deny。为true则报错
最后pcb_fd_install将全局打开表表项下标安装并返回文件描述符

file.c之file_close


/* 关闭文件 */
int32_t file_close(struct file* file) {
   if (file == NULL) {
      return -1;
   }
   file->fd_inode->write_deny = false;
   inode_close(file->fd_inode);
   file->fd_inode = NULL;   // 使文件结构可用
   return 0;
}

关闭文件,需要回收inode,回收打开文件表表项,回收PCB的文件描述符数组。
显然,file_close回收了inode,清理了打开文件表表项。
inode=null表示该打开文件表表项不存在

fs.c之sys_close


/* 将文件描述符转化为文件表的下标 */
uint32_t fd_local2global(uint32_t local_fd) {
   struct task_struct* cur = running_thread();
   int32_t global_fd = cur->fd_table[local_fd];  
   ASSERT(global_fd >= 0 && global_fd < MAX_FILE_OPEN);
   return (uint32_t)global_fd;
} 

/* 关闭文件描述符fd指向的文件,成功返回0,否则返回-1 */
int32_t sys_close(int32_t fd) {
   int32_t ret = -1;   // 返回值默认为-1,即失败
   if (fd > 2) {
      uint32_t global_fd = fd_local2global(fd);
      	 ret = file_close(&file_table[global_fd]);
      running_thread()->fd_table[fd] = -1; // 使该文件描述符位可用
   }
   return ret;
}

fd_local2global:将文件描述符转化成打开文件表的下标
sys_close:将pcb的文件描述符数组对应项置-1。

main.c

int main(void) {
   put_str("I am kernel\n");
   init_all();

   intr_enable();
   process_execute(u_prog_a, "u_prog_a");
   process_execute(u_prog_b, "u_prog_b");   
	thread_start("k_thread_a", 31, k_thread_a, "argA ");
	thread_start("k_thread_b", 31, k_thread_b, "argB ");

	uint32_t fd = sys_open("/file1",O_RDONLY);
	printf("fd:%d\n", fd);
	sys_close(fd);
	printf("%d closed now\n", fd);
	while(1);
   return 0;
}

实验结果

操作系统真象还原实验记录之实验二十七:文件的打开、关闭、写、读、读写指针定位_第1张图片

file1已在根目录被创建,否则只读方式打开会报错
打开后,每个pcb的文件描述符数组前两位是.和…
操作系统真象还原实验记录之实验二十七:文件的打开、关闭、写、读、读写指针定位_第2张图片
注意一个问题:
上次实验我们创建了file1,返回的文件描述符虽然没有输出,但也是3,这次只读打开file1也是3。
原因是,这次调用之前已经重新开机。内存已经全部清0。
但是创建file1的inode已经永久被同步到磁盘inode表中,所以inode_open可以在inode表里将file1的inode找出来。

2. 实现文件写入

file.c之file_write


/* 把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1 */
int32_t file_write(struct file* file, const void* buf, uint32_t count) {
   if ((file->fd_inode->i_size + count) > (BLOCK_SIZE * 140))	{   // 文件目前最大只支持512*140=71680字节
      printk("exceed max file_size 71680 bytes, write file failed\n");
      return -1;
   }
   uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
   if (io_buf == NULL) {
      printk("file_write: sys_malloc for io_buf failed\n");
      return -1;
   }
   uint32_t* all_blocks = (uint32_t*)sys_malloc(BLOCK_SIZE + 48);	  // 用来记录文件所有的块地址
   if (all_blocks == NULL) {
      printk("file_write: sys_malloc for all_blocks failed\n");
      return -1;
   }

   const uint8_t* src = buf;        // 用src指向buf中待写入的数据 
   uint32_t bytes_written = 0;	    // 用来记录已写入数据大小
   uint32_t size_left = count;	    // 用来记录未写入数据大小
   int32_t block_lba = -1;	    // 块地址
   uint32_t block_bitmap_idx = 0;   // 用来记录block对应于block_bitmap中的索引,做为参数传给bitmap_sync
   uint32_t sec_idx;	      // 用来索引扇区
   uint32_t sec_lba;	      // 扇区地址
   uint32_t sec_off_bytes;    // 扇区内字节偏移量
   uint32_t sec_left_bytes;   // 扇区内剩余字节量
   uint32_t chunk_size;	      // 每次写入硬盘的数据块大小
   int32_t indirect_block_table;      // 用来获取一级间接表地址
   uint32_t block_idx;		      // 块索引

   /* 判断文件是否是第一次写,如果是,先为其分配一个块 */
   if (file->fd_inode->i_sectors[0] == 0) {
      block_lba = block_bitmap_alloc(cur_part);
      if (block_lba == -1) {
	 printk("file_write: block_bitmap_alloc failed\n");
	 return -1;
      }
      file->fd_inode->i_sectors[0] = block_lba;

      /* 每分配一个块就将位图同步到硬盘 */
      block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
      ASSERT(block_bitmap_idx != 0);
      bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
   }

   /* 写入count个字节前,该文件已经占用的块数 */
   uint32_t file_has_used_blocks = file->fd_inode->i_size / BLOCK_SIZE + 1;

   /* 存储count字节后该文件将占用的块数 */
   uint32_t file_will_use_blocks = (file->fd_inode->i_size + count) / BLOCK_SIZE + 1;
   ASSERT(file_will_use_blocks <= 140);

   /* 通过此增量判断是否需要分配扇区,如增量为0,表示原扇区够用 */
   uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;

/* 将写文件所用到的块地址收集到all_blocks,(系统中块大小等于扇区大小)
 * 后面都统一在all_blocks中获取写入扇区地址 */
   if (add_blocks == 0) { 
   /* 在同一扇区内写入数据,不涉及到分配新扇区 */
      if (file_has_used_blocks <= 12 ) {	// 文件数据量将在12块之内
	 block_idx = file_has_used_blocks - 1;  // 指向最后一个已有数据的扇区
	 all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
      } else { 
      /* 未写入新数据之前已经占用了间接块,需要将间接块地址读进来 */
	 ASSERT(file->fd_inode->i_sectors[12] != 0);
         indirect_block_table = file->fd_inode->i_sectors[12];
	 ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
      }
   } else {
   /* 若有增量,便涉及到分配新扇区及是否分配一级间接块表,下面要分三种情况处理 */
   /* 第一种情况:12个直接块够用*/
      if (file_will_use_blocks <= 12 ) {
      /* 先将有剩余空间的可继续用的扇区地址写入all_blocks */
	 block_idx = file_has_used_blocks - 1;
	 ASSERT(file->fd_inode->i_sectors[block_idx] != 0);
	 all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];

      /* 再将未来要用的扇区分配好后写入all_blocks */
	 block_idx = file_has_used_blocks;      // 指向第一个要分配的新扇区
	 while (block_idx < file_will_use_blocks) {
	    block_lba = block_bitmap_alloc(cur_part);
	    if (block_lba == -1) {
	       printk("file_write: block_bitmap_alloc for situation 1 failed\n");
	       return -1;
	    }

      /* 写文件时,不应该存在块未使用但已经分配扇区的情况,当文件删除时,就会把块地址清0 */
	    ASSERT(file->fd_inode->i_sectors[block_idx] == 0);     // 确保尚未分配扇区地址
	    file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;

	    /* 每分配一个块就将位图同步到硬盘 */
	    block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
	    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

	    block_idx++;   // 下一个分配的新扇区
	 }
      } else if (file_has_used_blocks <= 12 && file_will_use_blocks > 12) { 
	 /* 第二种情况: 旧数据在12个直接块内,新数据将使用间接块*/

      /* 先将有剩余空间的可继续用的扇区地址收集到all_blocks */
	 block_idx = file_has_used_blocks - 1;      // 指向旧数据所在的最后一个扇区
	 all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];

	 /* 创建一级间接块表 */
	 block_lba = block_bitmap_alloc(cur_part);
	 if (block_lba == -1) {
	    printk("file_write: block_bitmap_alloc for situation 2 failed\n");
	    return -1;
	 }

	 ASSERT(file->fd_inode->i_sectors[12] == 0);  // 确保一级间接块表未分配
	 /* 分配一级间接块索引表 */
	 indirect_block_table = file->fd_inode->i_sectors[12] = block_lba;

//自己新增,同步间接块位图,原书没有
	block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
	    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

	 block_idx = file_has_used_blocks;	// 第一个未使用的块,即本文件最后一个已经使用的直接块的下一块
	 while (block_idx < file_will_use_blocks) {
	    block_lba = block_bitmap_alloc(cur_part);
	    if (block_lba == -1) {
	       printk("file_write: block_bitmap_alloc for situation 2 failed\n");
	       return -1;
	    }

	    if (block_idx < 12) {      // 新创建的0~11块直接存入all_blocks数组
	       ASSERT(file->fd_inode->i_sectors[block_idx] == 0);      // 确保尚未分配扇区地址
	       file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
	    } else {     // 间接块只写入到all_block数组中,待全部分配完成后一次性同步到硬盘
	       all_blocks[block_idx] = block_lba;
	    }

	    /* 每分配一个块就将位图同步到硬盘 */
	    block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
	    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

	    block_idx++;   // 下一个新扇区
	 }
	 ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);      // 同步一级间接块表到硬盘
      } else if (file_has_used_blocks > 12) {
	 /* 第三种情况:新数据占据间接块*/
	 ASSERT(file->fd_inode->i_sectors[12] != 0); // 已经具备了一级间接块表
	 indirect_block_table = file->fd_inode->i_sectors[12];	 // 获取一级间接表地址

	 /* 已使用的间接块也将被读入all_blocks,无须单独收录 */
	 ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 获取所有间接块地址

	 block_idx = file_has_used_blocks;	  // 第一个未使用的间接块,即已经使用的间接块的下一块
	 while (block_idx < file_will_use_blocks) {
	    block_lba = block_bitmap_alloc(cur_part);
	    if (block_lba == -1) {
	       printk("file_write: block_bitmap_alloc for situation 3 failed\n");
	       return -1;
	    }
	    all_blocks[block_idx++] = block_lba;

	    /* 每分配一个块就将位图同步到硬盘 */
	    block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
	    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
	 }
	 ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);   // 同步一级间接块表到硬盘
      } 
   }

   /* 用到的块地址已经收集到all_blocks中,下面开始写数据 */
   bool first_write_block = true;      // 含有剩余空间的扇区标识
   file->fd_pos = file->fd_inode->i_size - 1;   // 置fd_pos为文件大小-1,下面在写数据时随时更新
   while (bytes_written < count) {      // 直到写完所有数据
      memset(io_buf, 0, BLOCK_SIZE);
      sec_idx = file->fd_inode->i_size / BLOCK_SIZE;
      sec_lba = all_blocks[sec_idx];
      sec_off_bytes = file->fd_inode->i_size % BLOCK_SIZE;
      sec_left_bytes = BLOCK_SIZE - sec_off_bytes;

      /* 判断此次写入硬盘的数据大小 */
      chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;
      if (first_write_block) {
	 ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
	 first_write_block = false;
      }
      memcpy(io_buf + sec_off_bytes, src, chunk_size);
      ide_write(cur_part->my_disk, sec_lba, io_buf, 1);
      
      printk("file write at lba 0x%x\n", sec_lba);  //调式,完成后去掉

      src += chunk_size;   // 将指针推移到下个新数据
      file->fd_inode->i_size += chunk_size;  // 更新文件大小
      file->fd_pos += chunk_size;   
      bytes_written += chunk_size;
      size_left -= chunk_size;
   }
   inode_sync(cur_part, file->fd_inode, io_buf);
   sys_free(all_blocks);
   sys_free(io_buf);
   return bytes_written;
}

函数功能:把buf中的count个字节写入file

如果第一次写,先分配一个块并同步位图。

代码的思路是以扇区为单位依次写,所以先用all_block存储所有要写的扇区,最后再根据all_block依次写。

如果不需要分配块,即add_block=0,那么要写入的扇区地址必然只有一个,这个地址要么在inode的12个直接索引地址里,要么在间接索引块里,读入all_block就好了,再用block_idx指向该地址。

如果需要分配块,分三个情况
情况1:新数据12个直接块够用,剩余空间继续可用的扇区地址单独收录,要新分配的扇区向block_bitmap申请一个,all_block和i_sector同时收录一个,位图同步一个。

情况2:部分新数据写入前12个,部分新数据写入13个以上。
首先有剩余空间的扇区地址先单独收录于all_block,因为不需要新分配,不需要同步位图。

然后申请一个块当作间接索引块,地址写入i_sector[12],同步位图

(注意,这里的代码没有同步位图,但是必须要申请一个扇区,就要同步一次位图,所以我把它加上,加在书上P645的第337行代码处。)

然后再申请扇区当作新数据的扇区,属于直接索引的新分配的扇区的地址由i_sector和all_block同时收录,属于间接索引的,all_block收录,位图也是依次同步。最后在内存all_block[12]处的一扇区大小,统一写到间接索引块同步。

情况3:所有新数据都位于间接索引块,那就先把间接索引块读入all_block[12]处,这样含有剩余空间的扇区同时也被收录了,无需再单独收录,然后再不断申请新扇区,不断收录在all_block,不断同步位图。最后再在all_block[12]处同步间接索引块。

当all_block收集完所有要写入的扇区地址后,开始写。
all_block[i_size / 512]就是第一个要写入的扇区地址,这个扇区写之前往往有数据,
i_size % 512就是该扇区已有数据的最后一个字节偏移位置,512减去它就是剩余空闲字节。
要想接着写剩下的数据,采用的方法是,
先把整个扇区的数据读到缓冲区,然后再用memcpy拼接,最后再整块写入。

写入后,更新i_size等等、循环接着写。

fs.c之sys_write


/* 将buf中连续count个字节写入文件描述符fd,成功则返回写入的字节数,失败返回-1 */
int32_t sys_write(int32_t fd, const void* buf, uint32_t count) {
   if (fd < 0) {
      printk("sys_write: fd error\n");
      return -1;
   }
   if (fd == stdout_no) {  
      /* 标准输出有可能被重定向为管道缓冲区, 因此要判断 */
	 char tmp_buf[1024] = {0};
	 memcpy(tmp_buf, buf, count);
	 console_put_str(tmp_buf);
	 return count;
      }
      uint32_t _fd = fd_local2global(fd);
      struct file* wr_file = &file_table[_fd];
      if (wr_file->fd_flag & O_WRONLY || wr_file->fd_flag & O_RDWR) {
	 uint32_t bytes_written  = file_write(wr_file, buf, count);
	 return bytes_written;
      } else {
	 console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY\n");
	 return -1;
      }
 }

若fd等于stdout_no,往屏幕上打印,否则调用file_write往文件写。

syscall.c

/* 把buf中count个字符写入文件描述符fd */
uint32_t write(int32_t fd, const void* buf, uint32_t count) {
   return _syscall3(SYS_WRITE, fd, buf, count);
}

_syscall1改成了syscall3

stdio.c


/* 格式化输出字符串format */
uint32_t printf(const char* format, ...) {
   va_list args;
   va_start(args, format);	       // 使args指向format
   char buf[1024] = {0};	       // 用于存储拼接后的字符串
   vsprintf(buf, format, args);
   va_end(args);
   return write(1, buf, strlen(buf)); 
}

write也换成了三个参数

main.c

int main(void) {
   put_str("I am kernel\n");
   init_all();

   intr_enable();
   process_execute(u_prog_a, "u_prog_a");
   process_execute(u_prog_b, "u_prog_b");   
	thread_start("k_thread_a", 31, k_thread_a, "argA ");
	thread_start("k_thread_b", 31, k_thread_b, "argB ");

	uint32_t fd = sys_open("/file1",O_RDWR);
	printf("fd:%d\n", fd);
	sys_write(fd, "hello,world\n",12);
	sys_close(fd);
	printf("%d closed now\n", fd);
	while(1);
   return 0;
}

O_RDWR方式打开file1
再加了个
sys_write(fd, “hello,world\n”,12);
write接受的fd为0代表标准输入,为1代表标准输出,为2代表标准错误,我们的file1的fd是3,由sys_open返回,这样就可以根据当前PCB的文件描述符fd找到file1对应的的打开文件表表项。
另外每个PCBfd 等于0 、1 、2均指向目录,读写操作是无意义的。

printf里的write给fd传的是1,也就是向屏幕打印

实验结果

操作系统真象还原实验记录之实验二十七:文件的打开、关闭、写、读、读写指针定位_第3张图片
梳理一下几个函数:
write是系统调用,会依靠宏中断调用sys_write,是程序员用的函数
sys_write既可以往屏幕上打印,也可以往文件里写。
当往文件写的时候,调用的是file_write
printf也是系统调用,它是根据接受的可变参数,先格式化出打印的字符串,再调用write。printf也是程序员使用的函数,向屏幕打印,所以write传的fd固定写死是1。

3.文件的读取

file.c之file_read


/* 从文件file中读取count个字节写入buf, 返回读出的字节数,若到文件尾则返回-1 */
int32_t file_read(struct file* file, void* buf, uint32_t count) {
   uint8_t* buf_dst = (uint8_t*)buf;
   uint32_t size = count, size_left = size;

   /* 若要读取的字节数超过了文件可读的剩余量, 就用剩余量做为待读取的字节数 */
   if ((file->fd_pos + count) > file->fd_inode->i_size)	{
      size = file->fd_inode->i_size - file->fd_pos;
      size_left = size;
      if (size == 0) {	   // 若到文件尾则返回-1
	 return -1;
      }
   }

   uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
   if (io_buf == NULL) {
      printk("file_read: sys_malloc for io_buf failed\n");
   }
   uint32_t* all_blocks = (uint32_t*)sys_malloc(BLOCK_SIZE + 48);	  // 用来记录文件所有的块地址
   if (all_blocks == NULL) {
      printk("file_read: sys_malloc for all_blocks failed\n");
      return -1;
   }

   uint32_t block_read_start_idx = file->fd_pos / BLOCK_SIZE;		       // 数据所在块的起始地址
   uint32_t block_read_end_idx = (file->fd_pos + size) / BLOCK_SIZE;	       // 数据所在块的终止地址
   uint32_t read_blocks = block_read_start_idx - block_read_end_idx;	       // 如增量为0,表示数据在同一扇区
   ASSERT(block_read_start_idx < 139 && block_read_end_idx < 139);

   int32_t indirect_block_table;       // 用来获取一级间接表地址
   uint32_t block_idx;		       // 获取待读的块地址 

/* 以下开始构建all_blocks块地址数组,专门存储用到的块地址(本程序中块大小同扇区大小) */
   if (read_blocks == 0) {       // 在同一扇区内读数据,不涉及到跨扇区读取
      ASSERT(block_read_end_idx == block_read_start_idx);
      if (block_read_end_idx < 12 ) {	   // 待读的数据在12个直接块之内
	 block_idx = block_read_end_idx;
	 all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
      } else {		// 若用到了一级间接块表,需要将表中间接块读进来
	 indirect_block_table = file->fd_inode->i_sectors[12];
	 ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
      }
   } else {      // 若要读多个块
   /* 第一种情况: 起始块和终止块属于直接块*/
      if (block_read_end_idx < 12 ) {	  // 数据结束所在的块属于直接块
	 block_idx = block_read_start_idx; 
	 while (block_idx <= block_read_end_idx) {
	    all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx]; 
	    block_idx++;
	 }
      } else if (block_read_start_idx < 12 && block_read_end_idx >= 12) {
   /* 第二种情况: 待读入的数据跨越直接块和间接块两类*/
       /* 先将直接块地址写入all_blocks */
	 block_idx = block_read_start_idx;
	 while (block_idx < 12) {
	    all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
	    block_idx++;
	 }
	 ASSERT(file->fd_inode->i_sectors[12] != 0);	    // 确保已经分配了一级间接块表

      /* 再将间接块地址写入all_blocks */
	 indirect_block_table = file->fd_inode->i_sectors[12];
	 ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);	      // 将一级间接块表读进来写入到第13个块的位置之后
      } else {	
   /* 第三种情况: 数据在间接块中*/
	 ASSERT(file->fd_inode->i_sectors[12] != 0);	    // 确保已经分配了一级间接块表
	 indirect_block_table = file->fd_inode->i_sectors[12];	      // 获取一级间接表地址
	 ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);	      // 将一级间接块表读进来写入到第13个块的位置之后
      } 
   }

   /* 用到的块地址已经收集到all_blocks中,下面开始读数据 */
   uint32_t sec_idx, sec_lba, sec_off_bytes, sec_left_bytes, chunk_size;
   uint32_t bytes_read = 0;
   while (bytes_read < size) {	      // 直到读完为止
      sec_idx = file->fd_pos / BLOCK_SIZE;
      sec_lba = all_blocks[sec_idx];
      sec_off_bytes = file->fd_pos % BLOCK_SIZE;
      sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
      chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;	     // 待读入的数据大小

      memset(io_buf, 0, BLOCK_SIZE);
      ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
      memcpy(buf_dst, io_buf + sec_off_bytes, chunk_size);

      buf_dst += chunk_size;
      file->fd_pos += chunk_size;
      bytes_read += chunk_size;
      size_left -= chunk_size;
   }
   sys_free(all_blocks);
   sys_free(io_buf);
   return bytes_read;
}

file是打开文件表的表项,它的fd_pos指向了从文件的多少偏移字节开始读,读取和写入差不多但要简单一些,同样是all_block收录所有要读的扇区地址,和写不同的是,要读的扇区地址全部存储在直接索引或间接索引块中,早已分配。
如果读的数据在同一扇区内,这个扇区地址位于直接索引或间接索引块,all_block要么收录一个直接索引,要么收录整个间接索引块
如果读的数据跨多个块
情况一:都属于直接块,对应直接索引依次收录于all_block
情况二:部分属于直接块,部分属于间接索引块,all_block收录对应的直接索引,再收录整个间接索引块
情况三:全部属于间接索引块,all_block[12]处收录整个间接索引块

接下来就是从all_block的地址处依次读,和写数据一样
就是把file->fd_inode->i_size改成了file->fd_pos。
一边读,fd_pos也在一边增加。这和i_size一样。

fs.c之sys_read

int32_t sys_read(int32_t fd, void* buf, uint32_t count) {
	if(fd<0){
		printk("sys_read: fd error\n");
		return -1;
	}
	ASSERT(buf != NULL);
	uint32_t _fd = fd_local2global(fd);
	return file_read(&file_table[_fd], buf, count);
}

暂时没有添加标准输入,只是调用了file_read。
注意,read是读到内存,进内存是输入,出内存是输出,比如向屏幕打印那是写显存,显存相对于内存是外存,就是输出。

main.c

int main(void) {
   put_str("I am kernel\n");
   init_all();

   intr_enable();
   process_execute(u_prog_a, "u_prog_a");
   process_execute(u_prog_b, "u_prog_b");   
	thread_start("k_thread_a", 31, k_thread_a, "argA ");
	thread_start("k_thread_b", 31, k_thread_b, "argB ");

	uint32_t fd = sys_open("/file1",O_RDWR);
	printf("open /file1,fd:%d\n", fd);
	char buf[64] = {0};	
	int read_bytes = sys_read(fd, buf, 18);
	printf("1_ read %d bytes:\n%s\n", read_bytes, buf);

	memset(buf, 0, 64);	
	read_bytes = sys_read(fd, buf, 6);
	printf("2_ read %d bytes:\n%s", read_bytes, buf);
	
		
	memset(buf, 0, 64);	
	read_bytes = sys_read(fd, buf, 6);
	printf("3_ read %d bytes:\n%s", read_bytes, buf);
	
	printf("_____ close file1 and reopen ______\n");
	sys_close(fd);
	

	fd = sys_open("/file1",O_RDWR);
	memset(buf, 0, 64);	
	read_bytes = sys_read(fd, buf, 24);
	printf("4_ read %d bytes:\n%s", read_bytes, buf);
	sys_close(fd);
		
	while(1);
   return 0;
}

实验效果(读写相互验证)

操作系统真象还原实验记录之实验二十七:文件的打开、关闭、写、读、读写指针定位_第4张图片

4.文件读写指针定位

fs.h增加

/* 文件读写位置偏移量 */
enum whence {
   SEEK_SET = 1,
   SEEK_CUR,
   SEEK_END
};

fs.c之sys_lseek


/* 重置用于文件读写操作的偏移指针,成功时返回新的偏移量,出错时返回-1 */
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence) {
   if (fd < 0) {
      printk("sys_lseek: fd error\n");
      return -1;
   }
   ASSERT(whence > 0 && whence < 4);
   uint32_t _fd = fd_local2global(fd);
   struct file* pf = &file_table[_fd];
   int32_t new_pos = 0;   //新的偏移量必须位于文件大小之内
   int32_t file_size = (int32_t)pf->fd_inode->i_size;
   switch (whence) {
      /* SEEK_SET 新的读写位置是相对于文件开头再增加offset个位移量 */
      case SEEK_SET:
	 new_pos = offset;
	 break;

      /* SEEK_CUR 新的读写位置是相对于当前的位置增加offset个位移量 */
      case SEEK_CUR:	// offse可正可负
	 new_pos = (int32_t)pf->fd_pos + offset;
	 break;

      /* SEEK_END 新的读写位置是相对于文件尺寸再增加offset个位移量 */
      case SEEK_END:	   // 此情况下,offset应该为负值
	 new_pos = file_size + offset;
   }
   if (new_pos < 0 || new_pos > (file_size - 1)) {	 
      return -1;
   }
   pf->fd_pos = new_pos;
   return pf->fd_pos;
}

设置文件对应打开文件表的fd_pos即读写指针。
SEEK_SET代表文件开头+offet
SEEK_CUR代表文件当前fd_pos+offet
SEEK_END代表文件末尾+offet
文件末尾指的是文件最后一个字节的下一个字节。
EOF就是-1。

main.c

int main(void) {
   put_str("I am kernel\n");
   init_all();

   intr_enable();
   process_execute(u_prog_a, "u_prog_a");
   process_execute(u_prog_b, "u_prog_b");   
	thread_start("k_thread_a", 31, k_thread_a, "argA ");
	thread_start("k_thread_b", 31, k_thread_b, "argB ");

	uint32_t fd = sys_open("/file1",O_RDWR);
	printf("open /file1,fd:%d\n", fd);
	char buf[64] = {0};	
	int read_bytes = sys_read(fd, buf, 18);
	printf("1_ read %d bytes:\n%s\n", read_bytes, buf);

	memset(buf, 0, 64);	
	read_bytes = sys_read(fd, buf, 6);
	printf("2_ read %d bytes:\n%s", read_bytes, buf);
	
		
	memset(buf, 0, 64);	
	read_bytes = sys_read(fd, buf, 6);
	printf("3_ read %d bytes:\n%s", read_bytes, buf);
	
	

	//fd = sys_open("/file1",O_RDWR);
	printf("_____ SEEK_SET 0 ______\n");
	sys_lseek(fd, 0 ,SEEK_SET);	
	memset(buf, 0, 64);	
	read_bytes = sys_read(fd, buf, 24);
	printf("4_ read %d bytes:\n%s", read_bytes, buf);
	sys_close(fd);
		
	while(1);
   return 0;
}

读完后不再重新打开文件
sys_lseek(fd, 0 ,SEEK_SET); 设置读写指针。

实验结果

操作系统真象还原实验记录之实验二十七:文件的打开、关闭、写、读、读写指针定位_第5张图片
问题不大。

你可能感兴趣的:(操作系统,linux,操作系统)