Linux应用编程(一)文件IO及简单的文件读写

文章目录

  • (一)什么是文件IO
  • (二)文件操作的主要接口API
    • 1.什么是操作系统API
    • 2.linux常用文件IO接口
  • (三)文件操作的一般步骤
  • (四)重要概念:文件描述符
    • 1.打开文件与关闭文件
    • 2.实时查man手册
    • 3.读取文件内容
    • 4.向文件中写入
  • (六)open函数的flag详解
    • 1.读写权限:O_RDONLY O_WRONLY O_RDWR
    • 2.打开存在并有内容的文件时:O_APPEND、O_TRUNC
    • 3.exit、_exit、_Exit退出进程
    • 4.打开不存在的文件时:O_CREAT、O_EXCL
    • 5.O_NONBLOCK
    • 6.O_SYNC
  • (七)实例代码

(一)什么是文件IO

  1. IO就是input/output,输入/输出。文件IO的意思就是读写文件。

(二)文件操作的主要接口API

1.什么是操作系统API

  1. API是一些函数,这些函数是由linux系统提供支持的,由应用层程序来使用。
  2. 应用层程序通过调用API来调用操作系统中的各种功能,来干活。
  3. 学习一个操作系统,其实就是学习使用这个操作系统的API。
  4. 使用linux系统来读写文件,手段就是学习linux系统API中和文件IO有关的几个。

2.linux常用文件IO接口

  1. open、close、write、read、lseek

(三)文件操作的一般步骤

  1. 在linux系统中要操作一个文件,一般是先open打开一个文件,得到一个文件描述符,然后对文件进行读写操作(或其他操作),最后close关闭文件即可
  2. 强调一点:我们对文件进行操作时,一定要先打开文件,打开成功后才能去操作(如果打开本身失败,后面就不用操作了);最后读写完成之后一定要close关闭文件,否则可能会造成文件损坏。
  3. 文件平时是存在块设备中的文件系统中的,我们把这种文件叫静态文件。当我们去open打开一个文件时,linux内核做的操作包括:内核在进程中建立了一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内存中特定地址管理存放(叫动态文件)。
  4. 打开文件后,以后对这个文件的读写操作,都是针对内存中这一份动态文件的,而并不是针对静态文件的。当我们对动态文件进行读写后,此时内存中的动态文件和块设备中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。
  5. 常见的一些现象:
    • 第一个:打开一个大文件时比较慢
    • 第二个:我们写了一半的文件,如果没有点保存直接关机/断电,重启后文件内容丢失。
  6. 为什么要这么设计?
    • 以为块设备本身有读写限制(回忆NnadFlash、SD等块设备的读写特征),本身对块设备进行操作非常不灵活。而内存可以按字节为单位来操作,而且可以随机操作(内存就叫RAM,random),很灵活。所以内核设计文件操作时就这么设计了。

(四)重要概念:文件描述符

  1. 文件描述符其实实质是一个数字,这个数字在一个进程中表示一个特定的含义,当我们open打开一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护这个动态文件的这些数据结构挂钩绑定上了,以后我们应用程序如果要操作这一个动态文件,只需要用这个文件描述符进行区分。
  2. 一句话讲清楚文件描述符:文件描述符就是用来区分一个程序打开的多个文件的
  3. 文件描述符的作用域就是当前进程,出了当前进程这个文件描述符就没有意义了
    #(五) 一个简单的文件读写实例

1.打开文件与关闭文件

  1. linux中的文件描述符fd的合法范围是0或者一个正正数不可能是一个负数
  2. open返回的fd程序必须记录好,以后向这个文件的所有操作都要靠这个fd去对应这个文件,最后关闭文件时也需要fd去指定关闭这个文件。如果在我们关闭文件前fd丢掉了那就惨了,这个文件没法关闭了也没法读写了。

2.实时查man手册

  1. 当我们写应用程序时,很多API原型都不可能记得,所以要实时查询,用man手册
  2. man 1 xx查linux shell命令,man 2 xxx查API, man 3 xxx查库函数

3.读取文件内容

  1. ssize_t read(int fd, void *buf, size_t count);
  • fd:表示要读取哪个文件,fd一般由前面的open返回得到
  • buf:是应用程序自己提供的一段内存缓冲区,用来存储读出的内容
  • count:是我们要读取的字节数
  • 返回值ssize_t类型是linux内核用typedef重定义的一个类型(其实就是int),返回值表示成功读取的字节数。

4.向文件中写入

  1. ssize_t write(int fd, const void *buf, size_t count);
  2. 写入用write系统调用,write的原型和理解方法和read相似
  3. 注意const在buf前面的作用,结合C语言高级专题中的输入型参数和输出型参数一节来理解。
  4. 注意buf的指针类型为void,结合C语言高级专题中void类型含义的讲解
  5. 刚才先写入12字节,然后读出结果读出是0(但是读出成功了),这个问题的答案后面章节会讲,大家先思考一下。

(六)open函数的flag详解

1.读写权限:O_RDONLY O_WRONLY O_RDWR

  1. linux中文件有读写权限,我们在open打开文件时也可以附带一定的权限说明
  • 譬如O_RDONLY就表示以只读方式打开,O_WRONLY表示以只写方式打开,O_RDWR表示以可读可写方式打开
  1. 当我们附带了权限后,打开的文件就只能按照这种权限来操作。
  • 实例(如果,以只读当时打开文件,那么向文件写入数据就会出错)
    Linux应用编程(一)文件IO及简单的文件读写_第1张图片

2.打开存在并有内容的文件时:O_APPEND、O_TRUNC

  1. 思考一个问题:当我们打开一个已经存在并且内部有内容的文件时会怎么样?
  • 可能结果1:新内容会替代原来的内容(原来的内容就不见了,丢了)
  • 可能结果2:新内容添加在前面,原来的内容继续在后面
  • 可能结果3:新内容附加在后面,原来的内容还在前面
  • 可能结果4:不读不写的时候,原来的文件中的内容保持不变
  1. O_TRUNC属性去打开文件时,如果这个文件中本来是有内容的,则原来的内容会被丢弃。这就对应上面的结果1
  2. O_APPEND属性去打开文件时,如果这个文件中本来是有内容的,则新写入的内容会接续到原来内容的后面,对应结果3
  3. 默认不使用O_APPEND和O_TRUNC属性时就是结果4
  4. 如果O_APPEND和O_TRUNC同时出现会怎么样?
  • O_TRUNC会屏蔽掉O_APPEND的作用

3.exit、_exit、_Exit退出进程

  1. 当我们程序在前面步骤操作失败导致后面的操作都没有可能进行下去时,应该在前面的错误监测中结束整个程序,不应该继续让程序运行下去了。
  2. 我们如何退出程序?
  • 第一种;在main用return,一般原则是程序正常终止return 0,如果程序异常终止则return -1
  • 第二种:正式终止进程(程序)应该使用exit或者_exit或者_Exit之一

4.打开不存在的文件时:O_CREAT、O_EXCL

  1. open的flag O_CREAT就是为了应对打开一个并不存在的文件。O_CREAT就表示我们当前打开的文件并不存在,我们是要去创建并且打开它。
  2. 当我们open使用了O_CREAT,但是文件已经存在的情况下会怎样?经过实验验证发现结果是报错。
  3. 结论:open中加入O_CREAT后,不管原来这个文件存在与否都能打开成功,如果原来这个文件不存在则创建一个空的新文件,如果原来这个文件存在则会重新创建这个文件,原来的内容会被消除掉(有点类似于先删除原来的文件再创建一个新的)
  4. 这样可能带来一个问题?我们本来是想去创建一个新文件的,但是把文件名搞错了弄成了一个老文件名,结果老文件就被意外修改了。我们希望的效果是:如果我CREAT要创建的是一个已经存在的名字的文件,则给我报错,不要去创建。
  5. 这个效果就要靠O_EXCL标志和O_CREAT标志来结合使用。当这连个标志一起的时候,则没有文件时创建文件,有这个文件时会报错提醒我们(读取文件会出错)。
  6. open函数在使用O_CREAT标志去创建文件时,可以使用第三个参数mode来指定要创建的文件的权限。mode使用4个数字来指定权限的,其中后面三个很重要,对应我们要创建的这个文件的权限标志。譬如一般创建一个可读可写不可执行的文件就用0666
  • 如果不添加mode,默认创建的文件不可写

5.O_NONBLOCK

  1. 阻塞与非阻塞。如果一个函数是阻塞式的,则我们调用这个函数时当前进程有可能被卡住(阻塞住,实质是这个函数内部要完成的事情条件不具备,当前没法做,要等待条件成熟),函数被阻塞住了就不能立刻返回;如果一个函数是非阻塞式的那么我们调用这个函数后一定会立即返回,但是函数有没有完成任务不一定。
  2. 阻塞和非阻塞是两种不同的设计思路,并没有好坏。总的来说,阻塞式的结果有保障但是时间没保障;非阻塞式的时间有保障但是结果没保障。
  3. 操作系统提供的API和由API封装而成的库函数,有很多本身就是被设计为阻塞式或者非阻塞式的,所以我们应用程度调用这些函数的时候心里得非常清楚。
  4. 我们打开一个文件默认就是阻塞式的,如果你希望以非阻塞的方式打开文件,则flag中要加O_NONBLOCK标志
  5. 只用于设备文件,而不用于普通文件。

6.O_SYNC

  1. write阻塞等待底层完成写入才返回到应用层。
  2. 无O_SYNC时write只是将内容写入底层缓冲区即可返回,然后底层(操作系统中负责实现open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码)在合适的时候会将buf中的内容一次性的同步到硬盘中。这种设计是为了提升硬件操作的性能和销量,提升硬件寿命;但是有时候我们希望硬件不等待,直接将我们的内容写入硬盘中,这时候就可以用O_SYNC标志

(七)实例代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
	int fd = -1;             //fd 就是file descriptor,文件描述符
	int ret = -1;
	char buf[40] = {0};      //用户用来储存读取文件的数据的数组
	char write_buf[20] = "i love linux.\n"; //写入文件的数据
	//打开文件
	fd = open("c.txt", O_RDWR | O_APPEND |O_CREAT | O_EXCL, 0666);
	if(-1 == fd)
	{
		printf("读取文件失败.\n");
		//return -1;
		exit(-1);
	}
	else
	{
		printf("读取文件成功.\n");
	}
		
#if 1
	// 向文件中写入数据
	//ssize_t write(int fd, const void *buf, size_t count);
	ret = write(fd, write_buf, strlen(write_buf));
	if(ret < 0)
	{
		
		printf("写入文件错误.\n");
		exit(-1);
	}
	else
	{
		printf("文件写入成功,写入了%d 个字节.\n", ret);	
	}
#endif

#if 0
	//读取文件中的数据
	//ssize_t read(int fd, void *buf, size_t count);
	ret = read(fd, buf, 20);
	if (ret < 0)
	{
		printf("文件读取失败.\n");
		exit(-1);
	}
	else
	{
		printf("文件读取成功.\n");
		printf("内容为 [%s].\n", buf);
	}
#endif

	//关闭文件
	close(fd);
	exit(0);
	//return 0;
}

你可能感兴趣的:(linux应用编程及网络编程)