Linux 中对 系统级I/O 的初步理解

2 篇文章 0 订阅
2 篇文章 0 订阅

系统级I/O

打开和关闭文件
读和写文件
读取文件元数据
共享文件
I/O重定向

所有的I/O设备(例如网络,磁盘和终端)都被模型化为文件,而所有的输入输出都被当做对应文件的读和写来执行。

输入/输出(I/O)是在主存外部设备(例如磁盘驱动器,终端和网络)之间复制的过程。

输入操作: 从I/O设备复制到主存。
输出操作: 从主存复制数据到I/O设备。

打开和关闭文件

进程是通过调用open函数来打开已经存在的文件或者创建一个新的文件的:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int open(char *filename, int flags, mode_t mode);
返回:若成功则为新文件描述符,若出错为-1.

open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。

*filename:文件名
flags:打开方式,设置用户权限,指明了进程打算如何访问这个文件:

  • O_RDONLY:只读。
  • O_WRONLY:只写。
  • O_RDWR:可读可写。
    flags参数也可以是一个或者更多位掩码的或(|):
  • O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件。
  • O_TRUNC:如果文件已经存在,就截断它。
  • O_APPEND:在每次写操作前,设置文件位置到文件的尾处。

mode:指定了新文件的访问权限位。(若打开的文件已经存在,设置为0)

最后,进程通过调用close函数关闭一个打开的文件
**例题1:**下面程序的输出是什么?

#include "csapp.h"

int main()
{
	int fd1, fd2;
	fd1 = Open("foo.txt", O_RDONLY, 0) ;
	fd2 = Open("bar.txt", O_RDONLY, 0) ;
	Close(fd2) ;
	fd2 = Open("baz.txt", O_RDONLY, 0) ;
	printf("fd2 = %d\n", fd2) ;
	exit(0) ;
}

运行结果如下:

fd2 = 4

分析:

每打开一个文件,生成一个最小的正整数下标,即描述符;Linux会为每个进程自动打开三个文件:STDIN_FILENO(0),STDOUT_FILENO(1),STDERR_FILENO(2)。
所以fd1为打开的第四个文件,即描述符为3,fd2描述符为4,调用Close(fd2)关闭的文件fd2,所以文件描述符4也被关闭;随后又创建了一个新的文件fd2,描述符为4 。

读和写文件

应用程序是通过分别调用read和write函数来执行输入和输出的。

#include <unistd.h>

ssize_t read (int fd , void *buf , size_t n) ;
返回:若成功则为读的字节数,若EOF则为0,若出错为-1 。

ssize_t write (int fd , const void *buf , size_t n) ;
返回:若成功则为写的字节数,若出错则为-1 。

read函数:从描述符为 fd 的当前文件位置复制最多 n 个字节到内存位置 buf 。
write函数:从内存位置 buf 复制至多n个字节到描述符 fd 的当前文件的位置 。

读取文件元数据

应用程序通过调用 stat 和 fstat 函数,检索关于文件的信息 (有时也称为文件的元数据)。

#include <unistd.h>
#include <sys/stat.h>
int stat (const char *filename , struct stat *buf) ;
int fstat (int fd , struct stat *buf) ;
返回:若成功为0,若出错为-1 。

示例代码:
查询和处理一个文件的st_mode位

#include "csapp.h"
int main(int argc, char **argv)
{
	struct stat stat ;//用于存放文件信息的结构体
	char *type, *readok ;
	Stat(argv[1] , &stat) ;//调用stat函数,将文件argv[1]中的信息以stat结构体的形式存放到stat中
	if(S_ISREG(stat.st_mode))//是否为一个普通文件
		type = "regular";
	else if (S_ISDIR(stat.st_mode))//是否为一个目录文件
		type = "directory";
	else 
		type = "other";
	if((stat.st_mode & S_IRUSR))//用户是否可读
		readok = "yes";
	else 
		readok = "no";
	printf("type: %s, read: %s\n" , type , readok) ;
	exit(0) ;
}
共享文件

内核用三个相关的数据结构来表示打开的文件:

  • 描述符表:每个进程都有他独立的描述符表,它的表项是由进程打开的文件描述符来索引的。
  • 文件表:打开的文件的集合是由一张文件表来表示的,所有的进程共享这张表。
  • v-node 表:同文件表一样,所有的进程共享这张v-node 表。

下图,展示了一个示例,其中描述符1和4通过不同的打开文件表表项来引用两个不同的文件。这是一个典型的情况,没有共享文件,并且每个描述符对应一个不同的文件。
图一:
在这里插入图片描述
此外,多个描述符也可以通过不同的文件表表项来引用同一个文件。
例如: 下图,如果用一个filename调用open两次,就会发生这种情况。每个描述符都有它自己的文件位置,但是两个打开的文件件表表项共享同一个磁盘文件。
图二:在这里插入图片描述
假设 :在调用fork之前,父进程有如 图一 所示的打开文件。然后 图三 展示了调用fork后的情况。
在这里插入图片描述
由于 打开文件表 和 v-node表 都是所有进程共享 ,因此,子进程 继承了 父进程的打开文件,即父子进程共享相同的打开文件表集合 。

例题2:假设磁盘文件foobar.txt由6个ASCII码字符“foobar”组成,下列程序输出是什么?

#include "csapp.h"

int main ()
{
	int fd1, fd2 ;
	char c ;
	fd1 = Open("foobar.txt", O_RDONLY, 0) ;
	fd2 = Open("foobar.txt", O_RDONLY, 0) ;
	Read(fd1, &c, 1);
	Read(fd2, &c, 1);
	printf("c = %c\n", c) ;
	exit(0);
}

运行结构:

c = f

分析:
描述符fd1和fd2都有各自的打开文件表表项,所以每个描述符对于foobar.txt都有它自己的文件位置。因此,从fd2的读操作会读取foobar.txt的第一个字节,输出 c = f 。

例题3:

#include "csapp.h"

int main()
{
	int fd ;
	char c ;
	
	fd = Open("foobar.txt", O_RDONLY, 0) ;
	if (Fork() == 0) {
		Read(fd, &c, 1) ;
		exit(0) ;
	}
	Wait(NULL);
	Read(fd, &c, 1) ;
	printf("c = %c\n", c) ;
	exit(0) ;
}

运行结果:

c = o

该题类似于 图三 ;回想一下,子进程会继承父进程的描述表,以及所有进程共享的同一个打开文件表。因此,描述符fd在父进程中都指向同一个打开文件表表项。当子进程读取文件的第一个字节时,文件位加1。因此,父进程会读取第二个字节,而输出的就是 c = o 。

I/O重定向

什么叫I/O重定向?
即 重新定向 输出 的位置,重新定向 输入 的来源。

一般使用dup2函数来进行I/O重定向的工作。

#include <unistd.h>

int dup2(int oldfd, int newfd) ;
//返回:若成功则为非负的描述符,若出错则为-1.

dup2函数复制描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会复制oldfd之前关闭newfd。

如下图四,是在图一的基础之上,调用了dup2(4,1)函数的状态图结果:

在这里插入图片描述
两个文件描述符现在都指向文件B;文件A已经被关闭了,并且它的文件表和v-node表表项也已经被删除了;文件B的引用次数已经增加了。从此以后任何写到标准输出的数据都被重定向到文件B。
例题4:

#include "csapp.h"

int main()
{
	int fd1, fd2 ;
	char c ;
	fd1 = Open("foobar.txt", O_RDONLY, 0) ;
	fd2 = Open("foobar.txt", O_RDONLY, 0;
	Read(fd2, &c, 1) ;
	Dup2(fd2, fd1) ;
	Read(fd1, &c, 1) ;
	printf("c = %c\n", c) ;
	exit(0) ;
}

运行结果:

c = o

分析:Dup2(fd2, fd1) 将文件描述符fd1 重定向到了 fd2 ,所以 fd1与 fd2都指向同一个打开文件B,由于调用了Read(fd2, &c, 1) ,所以将光标位移动到了第二位,因此再次调用时,读取的是文件B的第二个字节o。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值