linux应用编程和网络编程-3.1.linux中的文件IO(read write open lseek)系统文件管理 inode 3种退出 fd 文件共享

基本文件API读写
文件IO的标准IO比较
文件IO
 read和write
 open及flag
 lseek
3种exit、error、perror
linux系统文件管理
文件描述符filedescriptor
文件共享
命令行中重定位命令>

基本文件API读写

3.1.1.应用编程框架介绍
(2)典型的嵌入式产品的研发过程就是:
  ①让linux系统在硬件上跑起来(系统移植工作)
  ②基于linux系统来开发应用程序实现产品功能。
(3)基于linux去做应用编程:通过调用linux的【系统API】来实现应用需要完成的任务。

3.1.1.4、什么是文件IO
(1)IO就是input/output,输入/输出。文件IO的意思就是读写文件。

3.1.2.文件操作的主要接口-----操作系统API

(1)API(应用程序接口)是一些【函数】,由linux操作系统提供支持的,由应用层程序来使用。
(2)应用层程序通过调用API来调用操作系统中的各种功能,来干活。
(3)学习一个操作系统,其实就是学习使用这个操作系统的API。

3.1.2.3、文件操作的一般步骤
(1)操作文件流程:open打开一个文件→得到fd→对文件进行操作→close关闭文件
如果打开了却没有close关闭文件
 ①内容留在buff中却没有释放buff→系统资源浪费
 ②更改后的内容没有写进文件中去
 ③fd用完,之后open会没有fd分配 //fd十有数量上限的
文件存在形式:
  静态文件:文件平时是存在硬盘【块设备】中的文件系统中的
  动态文件:open一个文件后,读取(复制一份)到内存中特定地址的文件
   内存中文件的读写都是针对动态文件的,而不是针对静态文件的

close操作:
  将内存中(修改过了的)动态文件去更新(同步)块设备中的静态文件

常见的一些现象:
 打开一个大文件时比较慢(因为内核要把【整个文件】从块设备读取到内存中,并且建立数据结构对其进行管理)
 
(6)为什么要这么设计(在内存中为什么要重新搞一份,而不是直接对块设备中的文件进行读写操作)?
 ①块设备本身有读写限制(回忆NnadFlash、SD等块设备的读写特征)
 ②对块设备操作不灵活(以块为单位读写),而内存可以以【字节为单位】来操作
 ③内存可以随机操作(内存就叫RAM,random,可以任意随机指定一个地址去操作)

文件IO的标准IO比较

文件IO:linux系统的API接口,由操作系统提供
     【不同操作系统中不通用→不可移植】
     【不带缓存】【效率不高】
     如open,close, read, write,ioctl 等。
     
标准IO:C库函数,由API封装得到,内部调用API来实现,但是比API更好用(多了一层封装)
     
     【在不同操作系统中几乎一样→可移植性】
     【带缓存】【性能比文件IO高】
     如fopen,fclose, fread, fwrite, freopen, fseek, ftell, rewind,ffulsh等。 
  封装主要是为了在应用层添加一个缓冲机制:通过fwrite写入的内容不是直接进入内核中的buf,而是先进入应用层标准IO库自己维护的buf中,然后标准IO库自己根据操作系统单次write的最佳count来选择好的时机来完成write到内核中的buf(内核中的buf再根据硬盘的特性来选择好的时机去最终写入硬盘中)。

FILE *fopen(const char *path, const char *mode);   返回文件指针
int open( const char * pathname, int oflags);      返回int类型的fd
返回类型不同
https://www.cnblogs.com/fnlingnzb-learner/p/7040726.html文件IO和标准IO的原型及使用说明

区分:
fd:           int open的返回值      文件描述符表的index
文件指针:       FILE * fopen的返回值    文件描述符表的表项
文件(偏移)位置指针:  当前文件偏移量(位于文件表中,不在vnode中)

文件IO

read和write

ssize_t read(int fd, void *buf, size_t count); 
	fd表示要读取哪个文件,fd一般由前面的open返回得到
	buf是应用程序自己提供的一段内存缓冲区,用来【存储】读出的内容
	count要读取的字节数
	返回值表示真正成功读取的字节数。
	返回值ssize_t类型是linux内核用typedef重定义的一个类型
	(其实就是当前平台下的int,为了构建平台可移植性),

ssize_t  write(int fd,const  void *buf, size_t count);
	从buf中读取count字节内容写入fd文件
	返回值:ssize_t:实际成功了多少个字节

有时候我们写正式程序时,我们要读取或者写入的是一个很庞大的文件
(譬如文件有2MB),我们不可能把count设置
2*1024*1024,而应该去把count设置为一个合适的数字(譬如2048、4096),
然后通过【多次循环读取】来实现全部读完。

注意buf的指针类型为void

open及flag

open操作:
  ①内核在进程中建立了一个打开文件的数据结构,记录下我们打开的这个文件;
  ②内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内存中【特定地址,由操作系统分配】管理存放。

open在手册中有两个函数原型, 如下所示:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
c语言不支持函数重载
 当我们调用open函数时, 实际上调用的是glibc封装的函数, 然后由glibc通过自陷指令, 进行真正的系统调用。 也就是说, 所有的系统调用都要先经过glibc才会进入操作系统。 这样的话, 实际上是glibc提供了一个变参函数open来满足两个函数原型, 然后通过glibc的变参函数open实现真正的系统调用来调用原型二。

O_RDONLY (只读方式)   
O_WRONLY (只写方式)    
O_RDWR(可读可写)
O_APPEND 
   ①每次写之前,都将标志位移动到文件的末端
   原子操作:移动到末端+写数据。中间用lseek调位置在O_APPEND面前是没用的
   即O_APPEND打开+lseek调整位置+write→→还是写在末尾
   (读则可以用lseek调整位置后去特定位置读,写则必定在末尾写,lseeek没用)
   ②文件读写关联【文件偏移指针绑定关联】
O_TRUNC 若文件存在,则长度被截为0,属性不变 (原内容被舍弃)

当源文件不存在时:
O_CREAT 创建(若源文件存在则会被覆盖)
O_CREAT+O_EXCL 组合使用:没有文件时创建,有文件时报错提醒,还可以同时设定权限
fd=open("tst.txt",O_RDWR | O_CREAT,0666);  此处mode权限=0666

O_NONBLOCK以非阻塞式打开    而打开一个文件默认就是阻塞式    
 只用于设备文件(硬件器件之类,比如串口文件),而不用于普通文件(因为文件系统操作本身就是非阻塞的)

O_SYNC 以同步IO方式打开文件,强制刷新内核缓冲区到输出文件(为了数据安全)      硬件不要等待,直接将内容写入硬盘中
没有O_SYNC则write将内容写入底层缓冲区就返回(底层会在一个合适的时候将buff写入硬盘中)--提升硬件性能和寿命,但是不安全

文件读写关联O_APPEND
 重复打开同一个文件→2个独立的fd→2个独立的文件管理表→2个独立的文件位置指针→自己管自己的读写
 而在open时添加O_APPEND则会将两个fd的读写关联起来
 #define FILEPATH    "./a.txt"
 fd1=open(FILEPATH,O_RDWR | O_APPEND);
 fd2=open(FILEPATH,O_RDWR | O_APPEND);
  分别写的内部原理就是2个fd拥有不同的文件位置指针,并且彼此只考虑自己的位移。

  而O_APPEND标志可以让write和read函数内部多做一件事情,就是移动自己的文件位置指针的同
时也去把别人的文件位置指针同时移动。
(也就是说即使加了O_APPEND,fd1和fd2还是各自拥有一个独立的文件位置指针,
但是这两个文件位置指针关联起来了,一个动了会通知另一个跟着动)
  O_APPEND对文件位置指针的影响,对文件的读写是原子的。

lseek

(人为改变文件位置指针的位置)
  在内存里的动态文件中,我们会通过文件位置指针来表征这个正在操作的位置。即文件(描述符)表结构体里面的一个指针元素。这个文件位置指针不能被直接访问,linux系统用lseek函数来访问这个文件位置指针。

#include <sys/types.h>
#include<unistd.h>
off_t lseek(int fildes,off_t offset ,int whence);

参考位置whence:
 1. SEEK_SET文件头
 2. SEEK_CUR当前位置
 3. SEEK_END文件尾(相当于文件长度)
offset偏移量      正数表示向后偏移,为负数表示向前偏移(基于whence位置偏移)

lseek(int fildes,0,SEEK_SET);移到文件开头时:
lseek(int fildes,0,SEEK_END);移到文件尾时:  返回值即文件长度
lseek(int fildes,0,SEEK_CUR);取得目前文件位置相对初始位置的偏移量
 
返回值:
当调用成功时则返回目前文件位置指针的位置,也就是距离文件开头多少个字节。
若有错误则返回-1,errno 会存放错误代码。

空洞文件

用lseek构建空洞文件(里面有空白部分)
  我们打开一个文件后,用lseek往后跳过一段,再write写入一段,就会构成一个空洞文件(这个空洞文件就是有一段没有内容)。
  空洞文件方法对多线程共同操作文件是及其有用的。有时候我们创建一个很大的文件(比如视频文件),如果从头开始依次构建时间很长。有一种思路就是将文件分为多段,然后多线程来操作每个线程负责其中一段的写入。
【就像修100公里的高速公路,分成20个段来修,每个段就只负责5公里,就可以大大提高效率】

3种exit、error、perror

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

errno全局变量和perror函数:
  errno就是error number,意思就是错误号码。由OS来维护的一个【全局变量】,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误。
  errno本身实质是一个int类型的数字,每个数字编号对应一种错误。当我们只看errno时只能得到一个错误编号数字(譬如-37),不适应于人看。 【有点类似于中断中的中断编号,也有点类似于一个数组】
解决方案就是linux系统提供了一个函数perror(意思print error),perror函数【内部,(不用给这个函数传参)】会读取errno并且将这个 成对应的错误信息字符串,然后print打印出来。 【eg:perror ("open: ");】
man 3 perror

linux系统文件管理

在这里插入图片描述

硬盘中的静态文件和inode(i节点)

(1)文件平时都以静态文件形式存放在硬盘中。
  比如一个块设备(硬盘)里有10000个块,一个块里有300个扇区,一个扇区有512个字节,则这个设备的总存储容量就是10000300512 B。
(2)一块硬盘中可以分为两大区域:
  ①硬盘内容存储管理表项:以文件为单位记录了各个文件的各种信息,每一个文件有一个信息列表数据结构(我们叫inode结构体,i节点,其实质是一个结构体,这个结构体有很多元素,每个元素记录了这个文件的一些信息,其中就包括文件名、文件在硬盘上对应的扇区号、块号那些东西·····)
  ②真正存储内容的区域。
  
访问硬盘文件的流程:
  操作系统访问硬盘时是先去读取硬盘内容管理表(作为搜找一个文件存储路径的索引),从中找到我们要访问的那个文件的扇区级别的信息,然后再通过这个信息去查询真正存储内容的区域,最后得到我们要的文件。

强调:硬盘管理的时候是以【文件】为基本单位的,每个文件一个inode结构体,每个inode有一个数字编号,对应一个结构体,结构体中记录了各种信息。(我们操作系统拿到一个文件名后就会在硬盘内容管理表中利用文件名循环匹配硬盘内容管理表中的文件信息节点inode,怎么匹配呢?我觉得应该是拿文件名去匹配节点里的一个结构体指向元素)
小结:i节点就是我们操作系统中用来记录文件各种信息(存储、大小之类)的一种数据结构,这种数据结构能够让我们去管理硬盘上的存储文件。

2种硬盘格式化:
  ①快速格式化【很快】:只删除了U盘中的硬盘内容管理表(其实就是inode),真正存储的内容没有动。
这种格式化的内容是有可能被找回的(我们可以重新扫描各个扇区,然后对各个扇区的内容文件进行重新的管理组建,形成一个新的内容管理表)。
  ②底层格式化【很慢】:

打开一个文件后的查询流程:
  所有的进程管理表→找到当前进程的信息[管理/信息]表(结构体)→里面的一个指针元素指向文件管理表(即文件描述符表,数组,index=fd,元素为文件表指针)→文件表指针指向文件表(找到具体的文件)→文件表指向vnode结构体(包含文件位置指针、文件存储位置、存储大小等各种信息,专门用来管理已经被打开的文件的)→进而对文件进行操作

文件与【流】的概念
  (1)流(stream)对应自然界的水流。文件操作中,文件类似是一个大包裹,里面装了一堆字符,但是文件被读出/写入时都只能一个字符一个字符的进行,而不能一股脑儿的读写,那么一个文件中N多的个字符被挨个一次读出/写入时,这些字符就构成了一个字符流。
  (2)流这个概念是动态的,不是静态的。
  (3)编程中提到流这个概念,一般都是IO相关的。所以经常叫IO流。文件操作时就构成了一个IO流。

在这里插入图片描述

进程中的文件(描述符)表
  是个数组(不是链表)=fd(index)+文件表指针(value)

文件描述符filedescriptor

文件描述符fd:

int =进程表中文件描述符表的一个表项(或者说序号index)
  进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表。
  用来区分/标识一个应用程序已打开的多个动态文件的。
  作用域:当前进程(每一个进程都有自己的进程表,而fd是进程表的index→不同的进程的fd可能相同)

当我们open打开一个文件时,操作系统在内存中构建了一些【数据结构】来表示这个动态文件,然后【返回给应用程序】一个数字作为文件描述符,这个数字就和我们内存中维护这个动态文件的这些数据结构挂钩绑定上了,以后我们应用程序如果要操作这一个动态文件,只需要用这个文件描述符进行区分(这个过程有点类似于我们的一个变量名和变量地址的相互绑定,文件描述符类似于一个文件标号)。

文件描述符与包括相关信息(如文件的打开模式、文件的位置类型、文件的初始类型等)的文件对象(描述一个文件的信息的数据结构)相关联,这些信息被称作文件的上下文。

特征:
 仅限于Unix/linux系统
 基于文件描述符的I/O操作兼容POSIX标准。
 在UNIX、Linux的系统调用中,大量的系统调用都是依赖于文件描述符。(fd仅限unix/linux系统中使用)
 此外,在Linux系列的操作系统上,由于Linux的设计思想便是把一切设备都视作文件。因此,文件描述符为在该系列平台上进行设备相关的编程实际上提供了一个统一的方法。

fd数字分配规律:

open系统调用内部由操作系统自动分配
  ①将当前空闲的最小的自然数作为fd给出(也可以通过其他方式指定fd)
  标准输入stdin=0(如键盘)
  标准输出stdout=1(LCD显示器)
  standard error标准错误stderr=2
  (012默认被系统占用→默认用户进程能够拿到的最小fd=3)
  ②最大fd:在linux的早期版本中(0.11)fd最大是20。一般来说,每个进程最多可以打开 64 个文件(0-OPEN_MAX=0~63)。对于 FreeBSD Mac OS X 10.3 和 Solaris 9 来说,每个进程最多可以打开文件的多少取决于系统内存的大小,int 的大小,以及系统管理员设定的限制。Linux 2.4.22 强制规定最多不能超过 1,048,576 。
  
  printf函数其实就是默认输出到标准输出stdout上了。stdio中还有一个函数叫fprintf,这个函数就可以指定输出到哪个文件描述符中。
  int fprintf(FILE *stream, const char *format, …)
   fprintf(fp, “%s %s %s %d”, “We”, “are”, “in”, 2014);

获取文件描述符最常见的方法:
  ①open
  ②fork后通过从父进程继承(文件在父进程中打开→fd) 父子进程fd→→同一个文件表
  
  在由fcntl、dup和dup2子例程复制或拷贝某个进程时,会发生同样的复制过程。

在这里插入图片描述

文件共享

概念:
  同一个文件(同一个文件指的是同一个inode,同一个pathname)被多个独立的读写体(多个文件描述符)去同时(一个打开尚未关闭的同时另一个去操作)操作。

意义:通过文件共享来实现多线程同时操作同一个文件,减少读写时间,提升效率。
核心:弄出多个文件描述符指向同一个文件(vnode)

常见的有3种文件共享的情况:
 ①同一个进程中多次使用open打开同一个文件
 ②在不同进程中去分别使用open打开同一个文件(位于不同的进程中→两个fd的数字可以相同也可以不同)
 ③linux系统提供了dup和dup2两个API来让进程复制文件描述符。

文件描述符的复制-dup和dup2进行文件描述符复制
(2个fd指向同一个文件表—【即操作同一个文件—实现了文件共享】,而文件位置/偏移指针是文件表的一个元素,故2个fd使用同一个文件位置指针)
在这里插入图片描述

int dup(int oldfd);        //不能指定得到的fd,要按照操作系统fd分配原则得到
int dup2(int oldfd, int newfd);   //dup2修复dup缺陷,指定newfd
成功返回newfd,失败返回-1

(1)之前课程讲过0、1、2这三个fd被标准输入、输出、错误通道占用。而且我们可以关闭这三个
(2)close(1)关闭标准输出,关闭后我们printf输出到标准输出的内容就看不到了
(3)然后我们可以使用dup重新分配得到1这个fd,这时候就把oldfd打开的这个文件和我们1这个标准输出通道给绑定起来了。这就叫标准输出的重定位。
(4)close+dup配合进行实现文件的重定位。

命令行中重定位命令 >

见鸟哥P345 数据流重定向

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值