Linux系统编程与网络编程——文件IO介绍(一)

Linux基本文件IO

对普通计算机用户来说,文件就是存储在永久性存储器上的一段数据流,通常是可执行程序或者是某种格式的数据。文件放置于文件夹,文件夹放置于某个磁盘分区中,这是从普通计算机用户眼里看到的文件。

但linux操作系统中文件的概念,却远远不局限与此,文件是linux对大多数系统资源访问的接口。linux常见的文件类型:普通文件、目录文件、设备文件、管道文件、套接字和链接文件等等。

在linux中所有的进程,在内核中都有一个对应的结构体来描述这个进程task_struct,也叫做进程管理块PCB(process control block),这个结构体中有一个文件描述符表files_struct,用来保存该进程对应的所有文件描述符


文件访问

程序要访问一个文件,首先需要通过一个文件路径名来打开文件,当进程打开一个文件的时候,进程将获得一个非负整数标识,即“文件描述符”(file description)。通过文件描述符,可以对文件进行I/O处理。对文件执行I/O操作,有两种基本方式:一种是系统调用的I/O方法,另一种是标准C的文件I/O方法。

系统调用的I/O方法和标准C的I/O方法的区别是:
1、基于标准C的文件操作函数的名字都是以字母“f”开头,而系统调用函数则不用,例如 fopen() 对应于系统调用的 open() ;
2、系统调用I/O方法是更低一级的接口,通常完成相同的任务是,比使用基于标准c的I/O方法需要更多编码的工作量。
3、系统调用直接处理文件描述符,而标准C函数则处理 FILE* 类型的文件句柄。
4、系统调用的IO没有缓冲区,部分在内核空间运行,部分在用户空间运行。C标准IO大部分在用户空间运行。如果是普通文件用C标准IO,如果是管道文件之类的用系统调用IO
5、基于标准C的I/O方法替用户处理有关系统调用的细节,比如系统调用被信号中断的处理等等。

什么时候使用系统调用的I/O,什么时候使用标准C的I/O:
基于标准C的I/O方法显然给程序员提供了极大的方便,但是有些程序却不能使用基于标准C的I/O方法。比如使用缓冲技术使得网络通信陷入困境,因为它将干扰网络通讯所使用的通信协议。考虑到这两种I/O方法的不同,在使用终端或者通过文件交换信息时,通常采用基于标准C的I/O方法。而使用网络或者管道通信时,通常采用系统调用的I/O方法。

在这里插入图片描述


文件的创建、打开、关闭和刷新缓冲区

通过 open() 和 creat() 系统调用,都可以创建一个并打开一个文件,系统调用 open() 和 creat() 成功时,都
会返回一个非负数的文件描述符,使用 close() 函数可以关闭指定的文件描述符的文件。

在使用任何与文件相关的系统调用之前,程序应该包含fcntl.hunistd.h头文件,它们为最普遍的文件例程提供了函数原型和常数。

open()函数原型:
在这里插入图片描述
当open()调用成功后,它会返回一个新的独一无二的文件描述符

open()函数必须指定打开文件的flags标志,其中必须指定标志O_RDONLY、O_WRONLY和O_RDWR中的一个,其他标志都是可选的,可以将其于前面的三种标志之一进行运算以生成最终的标志。
在这里插入图片描述
源文件:open_test.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char** argv)
{
	int fd;
	fd = open("/tmp/open_test",O_CREAT|O_WRONLY|O_TRUNC,0640);
	if(fd == -1)
	{
		printf("open failed fd = %d\n", fd);
	} 
	else
	{
		printf("/tmp/open_test created, fd = [%d]\n", fd);
	}
	return 0;
}

以上代码中open()函数传入了3个参数:
——第一个参数是一个字符串参数,创建或者打开文件的路径。
——第二个参数指明了open操作需要创建一个新文件。
——第三个参数指明了新建文件的访问权限。

整数类型的变量fd记录了open()函数的返回值。如果fd等于-1,那么表示open()函数返回失败,否则fd记录
了系统返回的所新建并打开的文件描述符。在程序退出之前,使用close()来关闭文件。

一个进程默认打开3个文件描述符:
在这里插入图片描述


creat()函数原型:
在这里插入图片描述
使用creat替换掉open函数
在这里插入图片描述


close()函数原型:
在这里插入图片描述
close()系统调用关闭并释放一个文件描述符。close()成功时返回值等于0,错误时返回-1。但是程序一般不需要检查close系统调用的返回值。除非发生严重的程序错误。当进程退出的时候文件描述符会自动关闭


错误值:

open()和creat()系统调用成功时,都会返回一个新的文件描述符,当返回失败时,将返回-1。通过errno以及使用perror(), 以及strerror()可以查看错误信息。
在这里插入图片描述
perror里面的char *起到了一个信息分类的作用,如果你是open函数出错,可以perror(“open:”)这样使用,具体的错误信息会在open:后面输出。

可以在 /usr/include/asm-generic/errno.h 以及 /usr/include/asm-generic/errno-base.h 中去查看errno的具体
定义值。

open()和creat()常见错误信息:
在这里插入图片描述


刷新缓冲区fsync()函数原型:

由于linux内核对物理存储可能会有写延时,所以就算成功的关闭了一个文件,也不能保证数据都被成功写入
物理存储。当文件关闭时,对文件系统来说一般不去刷新缓冲区。

如果你要保证数据写入磁盘等物理存储设备中就使用fsync()。fsync()函数原型是:
在这里插入图片描述


文件的读写与读写位置

文件读read()函数原型:

文件打开后,我们可以使用read()来进行文件的读操作。这个系统调用的函数原型是:
在这里插入图片描述
三个参数分别是:
fd : 要进行读写操作的文件描述符。
buf :要写入文件内容或读出文件内容的内存地址。
count :要读写的字节数。

read()是从文件描述符fd所引用的文件中读取count字节到buf缓冲区中。

如果 read()成功读取了数据,就返回所读取的字节数目,否则返回-1。
如果 read()读到了文件的结尾或者被一个信号所中断,返回值会小于count。
如果 文件指针已经为于文件结尾,read()操作将返回0。

源文件:read_test.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char** argv)
{
	int fd_read;
	char buf[100];
	int ret;
	
	/判断是否传入文件名/
	if(argc < 2)
	{
		printf("Usage read_test FILENAME\n");
		return -1;
	}
	 fd_read = open(argv[1], O_RDONLY);
	if(fd_read == -1)
	{
		perror("open error");
		return -1;
	}
	
	 /读取数据/
	ret = read(fd_read, buf, sizeof(buf));
	printf("read return = [%d]\n", ret);
	buf[ret]='\0';
	
	/如果读出来数据,则打印数据/
	if(ret >= 0)
	{
		printf("========= buf =========\n");
		printf("%s\n", buf);
		printf("=======================\n");
	} 
	else
	{
		perror("read error");
	}
	close(fd_read);

	return 0;
}

当read()返回-1的时候,使用errno以及perror()可以查看读文件的错误信息。

我们经常需要检查的是EINTR错误,产生这个错误是由于 read()系统调用在读取任何数据前被信号所中断。发生这个错误的时候,文件指针并没有移动,我们需要重新读取数据。
在这里插入图片描述

read封装,对中断进行处理:
在这里插入图片描述


文件写write()函数原型:
在这里插入图片描述
write()从buf中写count字节到文件描述符fd所引用的文件中,成功时返回实际所写的字节数。

在实际的写入过程中,可能会出现写入的字节数少于count。这时返回的是实际写入的字节数。

所以调用write()后都必须检查返回值是否与要写入的相同,如果不同就要采取相应的设施。

源代码:write_test.c

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
int main(int argc, char** argv)
{
	int fd;
	char buf[] = "helloworld";
	int bytes_write;
	if(argc < 2)
	{
		printf("Usage: write_test FILEWRITE\n");
		return -1;
	}
	
	fd = open(argv[1], O_CREAT|O_WRONLY|O_TRUNC, 0640);
	
	if(fd == -1)
	{
		perror("create error");
		return -2;
	}
	int bytes_write = write(fd, buf, sizeof(buf));
	printf("bytes_write:%d \n", bytes_write);
	return 0;
}

在这里插入图片描述
write封装,对中断进行处理:
在这里插入图片描述


文件的读写位置:

每一个被打开的文件,都有一个文件指针表明当前的存取位置。一般文件被新建或者打开的时候,文件指针都位于文件头,除非在打开的时候指定了O_APPEND标志。文件的读写操作都是从文件指针位置开始的,每次文件的读取和写入,文件指针都会根据读写的字节数向后移动,直到文件结尾。

如果需要从文件的随机位置读写数据,那么就需要先移动文件指针。lseek()系统调用可以使文件指针移动到文件中的指定位置。

下面是lseek()的函数原型:
在这里插入图片描述
fd: 文件描述符
offset:移动的偏移量,单位为字节数
whence: 文件指针移动偏移量的解释,有三个选项,如下表:
在这里插入图片描述
lseek()移动文件指针成功时,将返回文件指针的当前位置。失败时返回-1。

下面的代码,通过文件指针移动到文件结尾,lseek()将返回文件指针的位置,这个指针偏移量的就是文件
大小。

源代码 lseek_test.c:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char** argv)
{
	int fd_lseek;
	char buf[100];
	int file_size;
	
	/判断是否传入文件名/
	if(argc < 2)
	{
		printf("Usage: lseek_test FILENAME\n");
		return -1;
	}
	
	/打开要读取的文件/
	fd_lseek = open(argv[1], O_RDONLY);
	if( fd_lseek == -1)
	{
		perror("open error:");
		return -1;
	}
	
	/移动指针到文件尾/
	file_size = lseek(fd_lseek, 0, SEEK_END);
	if(file_size >= 0)
	{
		printf("size of [%s] = %d\n", argv[1], file_size);
	}
	else
	{
		perror("lseek error");
	}
	close(fd_lseek);
	return 0;
}

如果想要在32位系统上操作大于2g的文件,你需要在包含任何头文件前添加宏#define_FILE_OFFSET_BITS 64


文件的访问权限

linux有严格的文件权限控制,当我们需要在程序中判断一个文件时否具有读、写等权限的时候,可以使用access()系统调用。
在这里插入图片描述
参数pathname时要判断的文件路径名。参数mode可以是以下值或者是他们的组合:
R_OK: 判断文件是否有读权限
W_OK: 判断文件是否有写权限
X_OK: 判断文件是否有可执行权限
F_OK:判断文件是否存在

当access()系统调用对文件的测试成功时返回0,只要有其中一个条件不符,则返回-1。


修改文件属性

当文件被打开之后,进程会获取一个文件描述符,文件描述符包含了文件描述符标志以及当前进程对文件的访问权限等信息状态标志。当我们需要获取或者修改文件描述符中包含的标志时,可以使用fcntl()系统调用。

fcntl()函数原型:
在这里插入图片描述
fcntl()是一个参数fd是文件描述符,第二个参数指定了函数的操作,第三个参数指定了修改为什么属性。只有当第二个参数为F_SETFL时才能使用第三个参数。fcntl()函数常用的功能有:
在这里插入图片描述


阻塞与非阻塞

阻塞读终端

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
	char buf[10];
	int n;
	
	n = read(STDIN_FILENO, buf, 9);
	if (n < 0)
	{
		perror("read STDIN_FILENO");
		exit(1);
	}
	buf[n] = '\0';
	
	write(STDOUT_FILENO, buf, n + 1);
	return 0;
}

非阻塞读终端

#
include <errno.h>
#include<stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char** argv)
{
	char buf[10];
	int ret;
	int flags = fcntl(STDIN_FILENO, F_GETFL);
	if(flags == -1)
	{
		perror("fcntl");
		return -1;
	}
	
	/设为非阻塞/
	flags |= O_NONBLOCK;
	
	if(fcntl(STDIN_FILENO, F_SETFL, flags) == -1)
	{
		perror("fcntl");
		return -1;
	}
	
	AGAIN:
	ret = read(STDIN_FILENO, buf, sizeof(buf));
	if(ret == -1)
	{
		if(errno == EAGAIN)
		{
			printf("#####read again#####\n");
			sleep(1);
			goto AGAIN;
		}
		perror("read");
		return -1;
	}
	
	ret = write(STDOUT_FILENO, buf, ret);
	if(ret == -1)
	{
		perror("write");
		return -1;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Gsunshine24/article/details/88765300