linux——文件IO

本文深入介绍了Linux系统中文件I/O的基本概念,包括文件描述符、文件指针和文件缓冲区。详细阐述了open、close、read、write等核心函数的使用,并讨论了阻塞与非阻塞的概念。举例说明了在不同文件类型(如普通文件和设备文件/dev/tty)上进行读写操作的区别。此外,还提到了文件属性获取、文件描述符复制以及其他相关系统调用如lseek、fcntl等。

文件IO

基本概念

系统IO原理

文件描述符:通过文件描述符可以找到文件的inode,通过inode可以找到文件的数据块
文件指针:读和写共享一个文件指针,读或者写都会引起指针的变化
文件缓冲区:读或者写会先通过文件缓冲区,主要目的是为了减少对磁盘的读写次数(缓冲区在内存,内存访问速度远远大于磁盘)

进程的虚拟地址空间分为用户区和内核区,其中内核区是受保护的,用户不能直接对其进行读写操作。内核区主要是内存管理、进程管理、设备驱动管理、虚拟文件系统
进程管理中有一个区域是PCB(结构体:task_struct)——进程控制块

相关函数

  • open
int open(const char* filename, int flags);
int open(const char* filename, int flags, mode_t mode);
param:
	filename: 文件名
	flag: 打开方式	O_RDONLY-只读
					O_WRONLY-只写
					O_RDWR-可读可写
					O_CREAT: 文件不存在时创建它,使用改参数时必须要加mode
文件的最终权限是:mode & (~umask)
可以用shell命令查看:umask
// 前三个必须有且只能有一个,剩下的可以是0或者多个,且只能是或
open(filename, O_RDWR | O_CREAT, 0777)
return:
	成功:返回一个最小且未被使用的文件描述符
	失败:返回-1,并设置errno

  • close
int close(int fd);
param:
	fd: 文件描述符
return:
	成功:0
	失败:-1,并设置errno

  • read
ssize_t read(int fd, void* buf, size_t count);
param:
	fd: 文件描述符
	buf: 读出来的数据保存在buf中
	count: buf缓冲区最大字节数
return:
	>0: 读取到的字节数
	=0: 文件读取完毕(读到文件结尾)
	<0: 出错,并设置errno

阻塞与非阻塞

阻塞和非阻塞不是read函数的属性,而是文件本身的属性

  • 对于普通文件,read函数是非阻塞的(read函数在读完文件内容后,再次调用read函数会立刻返回)。
  • 对于设备文件(/dev/tty),read函数是阻塞的(read函数会一直等待输入)。

/dev/tty为终端字符文件,该文件是对键盘,显示器的抽象,向该文件写入,则写入内容将被显示在显示器;读该文件,则将从键盘获得输入

非阻塞的例子

hello.txt里面有以下字符:
hello world\n
int main()
{
	int fd = open("hello.txt", O_RDWR | O_CREAT, 0755);
	char buf[1024];
	memset(buf, 0, sizeof(buf));
	int n = read(fd, buf, sizeof(buf));
	printf("n==[%d], buf==[%s]\n", n, buf);
	memset(buf, 0, sizeof(buf));
	n = read(fd, buf, sizeof(buf));
	printf("n==[%d], buf==[%s]\n", n, buf);
	close(fd);
	return 0;
}
n==[11], buf==[hello word
]
n==[0], buf==[]
可以看到,一次性读完文件内容在调用read读取文件时,直接返回0,而不是阻塞在那里

阻塞的例子

int main()
{
	int fd = open("/dev/tty", O_RDWR);
	char buf[1024];
	memset(buf, 0, sizeof(buf));
	int n = read(fd, buf, sizeof(buf));
	printf("n==[%d], buf==[%s]\n", n, buf);
	close(fd);
	return 0;
}
会阻塞在终端,一直等待用户的输入,如果用户不输入,则一直阻塞

  • write
ssize_t write(int fd, const void* buf, size_t count);
param:
	fd: 文件描述符
	buf: 要写入的数据
	count: buf中数据的长度
return:
	成功: 写入的字节数
	失败: 返回-1, 并设置errno

  • lseek——移动文件指针
#include<sys/types.h>
#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
param:
	fd: 文件描述符
	offset: 偏移量, 取决于whence
	whence: 	SEEK_SET-设置偏移量
				SEEK_CUR-设置相对当前的文件指针位置
				SEEK_END-设置相对于文件数据结尾(文件数据长度)偏移量
return:
	成功: 返回偏移量
	失败: 返回-1, 并设置errno

  • perror
// errno是每个进程都有的全局变量
#include<errno.h>
//perror()可以将errno对应的错误信息打印出来
perror(const char* msg);
such as----<msg: 错误信息>

  • stat/lstat——获取文件属性
//加了const一定是出参,不加可能是出参也可能是出参
int stat(const char* pathname, struct stat* buf);
param:
	pathname: 文件名
	buf: stat结构体,出参
return:
	成功: 0
	失败: -1
stat结构体成员可以用: man 2 stat 来查看
/*
对于普通文件来说,lstat和stat函数一样
对于软链接文件来说,lstat获取的是链接文件本身的属性;stat获取的是链接文件指向的文件的属性
*/

  • dup
int dup(int oldfd);
param:
	oldfd: 要复制的文件描述符
return:
	成功:返回最小且未被使用的文件描述符
	失败:返回-1,并设置errno
//newfd和oldfd都指向了同一个文件,内核会维护一个计数,这个计数会加1,只有当计数为0时,文件才会被关闭。

  • dup2
int dup2(int oldfd, int newfd);
param:
	oldfd: 原来的文件描述符
	newfd: 新的文件描述符
return:
	成功:将oldfd复制给newfd,两个文件描述符指向同一个文件
	失败:返回-1,并设置errno

当调用dup2函数后,若newfd已经打开了一个文件,则先关闭这个文件,然后newfd指向了和oldfd相同的文件
可以利用dup2实现文件重定向:
dup2(fd, STDIN);

  • fcntl——改变已经打开的文件属性
int fcntl(int fd, int cmd, ...);
param:
	fd: 文件描述符
	cmd:	F_DUPFD-复制文件描述符,同dup
			F_GETFL-获取文件描述符的flag属性
			F_SETFL-设置文件描述符的flag属性
return:
	cmd为F_DUPFD时:返回新的文件描述符
	cmd为F_GETFL时:返回文件描述符的flag值
	cmd为F_SETFL时:返回0
	失败:返回-1,并设置errno
复制文件描述符:newfd = fcntl(fd, F_DUPFD, 0);
获取文件的属性标志:flag = fcntl(fd, F_GETFL, 0);
设置文件状态标志:	flag = flag | O_APPEND;
				fcntl(fd, F_SETFL, flag);

  • opendir, readdir, closedir
读取目录的一般步骤:
DIR *pDir = opendir("dir");
while((p=readdir(pDir)) != NULL) { }
closedir(pDir);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值