Linux学习(1)——文件IO

一. 文件的基本操作(打开、定位、读写、关闭)

1.1 文件操作的基本顺序

打开 open
创建 create
定位 lseek
读 read
写 write
关闭 close

1.2 open函数

  1. 用于打开或者创建一个文件

  2. 函数原型

    int open( const char* pathname, int oflag, ... )
    
  3. 参数
    pathname: 要打开或者创建的文件描述符;
    oflag: 用于指定文件打开模式标识信息等;

    1. 文件打开模式标志( 必须且唯一 )
      O_RDONLY : 只读打开
      O_WRONLY: 只写打开
      “只写”并不意味着完全只能写入而不能被读取,而只是根据用户组的权限设置,“只写”所对应的用户组没有对该文件的读取权限,而其它用户组(例如超级用户root)可能具有读取该文件的权限。
      O_RDWR: 读写打开
    2. 其他文件标志( 可选 )
      O_APPEND: 每次写的数据都添加到文件尾
      O_TRUNC: 若此文件存在,并以读写或只写打开,则清空文件全部内容(即将其长度截短为0)
      O_CREAT: 若文件不存在,则创建该文件。此时,open函数需要第
      三个参数,用于指定该文件的访问权限位
      O_EXCL: 若同时指定了O_CREAT标志,而文件已经存在,则会出
      错。可用于测试文件是否存在

    … :可变参数,mode_t mode
    在这里插入图片描述
    参考: linux open函数的 mode_t涵义

  4. 返回值

整型数据:
成功时,返回文件文件描述符
出错时,返回-1

1.3 文件描述符

  1. 进程打开文件的内核数据结构
    进程打开文件的内核数据结构
    task_struct: 进程控制块( Process Control Block, PCB )
    files_struct: 包含了进程已打开的文件表, open函数返回的文件描述是符已打开文件表的索引
    file: 文件对象, 代表一个已打开的文件
    f_dentry: struct dentry *f_dentry; 文件相关目录项
    f_pos: 文件偏移量
    d_inode: struct inode *d_inode;

  2. 索引节点
    1)文件系统索引节点的信息,存储在磁盘上;
    2)当需要时,调入内存,填写VFS的索引节点(即inode结构)
    3)每个文件都对应了一个索引节点
    4)通过索引节点号,可以唯一的标识文件系统中的指定文件
    struct inode{

    unsigned long i_no;//索引节点号
    umode_t i_mode;//文件类型访问权限
    uid_t i_uid;//文件拥有者ID
    gid_t i_gid;//文件拥有者所在组ID
    off_t i_size;//文件大小
    time_t i_atime;//最后访问时间
    time_t i_mtime;//最后修改时间
    };

  3. 文件描述符
    1)文件描述符是已打开文件的索引, 通过该值可以在fd_array表中检索相应的文件对象
    2)文件描述符是一个非负的整数
    3)文件描述符0、1、2分别对应于标准输入、标准输出、标准出错,在进程创建时, 已经打开。

1.3 creat函数

(不是create)

  1. 用于创建一个新文件

  2. 函数原型

    int creat(const char *pathname, mode_t mode)
    
  3. 参数
    pathname: 要创建的文件名(包括路径信息)
    mode: 同open的第三个参数, 访问权限位

  4. 返回值
    成功返回只写打开的文件描述符
    出错返回-1

creat函数的功能可以用open函数实现:

   open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);
   //指定O_TRUNC标志是因为当文件存在时,调用creat函数,会将文件的大小变为0.

1.4 lseek函数

  1. lseek函数用于修改当前文件偏移量
    当前文件偏移量的作用当前文件偏移量的作用: 规定了从文件什么地方开始进行读、写操作
    通常,读、写操作结束时,会使文件偏移量增加读写的字节数
    当打开一个文件时,除非指定了O_APPEND标志,否则偏移量被设置为0

  2. 函数原型

    off_t lseek(int filedes, off_t offset, int whence)
    
  3. 参数
    filedes: open/creat函数返回的文件描述符;
    offset:
     相对偏移量:需结合whence才能计算出真正的偏移量
     类型off_t:通常情况下是32位数据类型
    whence: 该参数取值是三个常量之一
     SEEK_SET: 文件偏移量为距文件开始处的offset个字节
     SEEK_CUR: 文件偏移量为当前文件偏移量+offset(可正可负)
     SEEK_END: 文件偏移量为当前文件长度+offset(可正可负)

  4. 返回值
    若成功,返回新的文件偏移量
    若出错,返回-1

  5. 获得当前偏移量

    off_t CurrentPosition;
    CurrentPosition = lseek(fd, 0, SEEK_CUR);
    //fd: open或creat返回的文件描述符
    //0: 相对当前偏移量为0的位置
    

lseek操作并不引起任何I/O操作,只是修改内核中的记录。

  1. 空洞文件
      空洞文件特点就是offset大于实际大小,也就是说一个文件的两头有数据而中间为空,以‘\0‘填充。
      使用lseek修改文件偏移量后,当前文件偏移量有可能大于文件的长度在这种情况下,对该文件的下一次写操作,将加长该文件这样文件中形成了一个空洞。对空洞区域进行读,均返回0.

1.5 read函数

  1. 用于从文件中读出数据

  2. 函数原型

    ssize_t read(int fd, void *buff, size_t nbytes)
    
  3. 参数
    fd: open或creat返回的文件描述符
    buff: 指向缓冲区,用于存放从文件读出的数据
     void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据.
     void真正发挥的作用在于:
     (1) 对函数返回的限定;
     (2) 对函数参数的限定。
    nbytes:unsigned int;需要从文件中读出的字节数
     缓冲区的大小>=nbytes

  4. 返回值
    类型: ssize_t, 即int
    出错:返回-1
    成功:返回从文件中实际读到的字节数
     当读到文件结尾时,则返回0
     很多情况下,read实际读出的字节数都小于要求读出的字节数

1.6 write函数

  1. 用于向文件写入数据

  2. 函数原型

    ssize_t write( int fd, const void *buff, size_t nbytes );
    
  3. 参数
    fd:文件描述符
    buff:指向缓冲区,存放了需要写入文件的数据
    nbytes:需要写入文件的字节数

  4. 返回值
    返回值类型:ssize_t,即int
    出错:返回-1
    成功:返回实际写入文件的字节数
    write出错的原因:
     磁盘满
     没有访问权限
     超过了给定进程的文件长度限制

1.7 close函数

  1. 用于关闭一个已打开的文件
  2. 函数原型
  3. 参数
  4. 返回值

二. I/O效率

ssize_t read(int fd, void *buff, size_t nbytes)

nbytes的取值是影响I/O效率的关键。

原因
◼ Linux文件系统采用了某种预读技术
◼ 当检测到正在进行顺序读取时,系统就试图读入比应用程序所要求 的更多数据
◼ 并假设应用程序很快就会读这些数据
◼ 当BUFFSIZE增加到一定程度后,预读就停止了

三. 文件共享

3.1 进程之间的文件共享

  1. 两个独立的进程各自打开同一个文件
    在这里插入图片描述
    两个独立的进程打开文件时,操作系统内核中都会创建一个不同的文件表项(struct file结构体),但是这些不同的struct file结构体最终都会指向同一个struct inode结构体,因为一个文件只能对应唯一的一个struct inode结构体

    由于每次open都会创建不同的struct file结构体,也就是说不同的文件描述符对应着不同的文件表项(struct file结构体)。所以每个进程有各自独立的当前文件偏移量,互不影响

    举个例子,两个独立的进程打开了同一文件,进程A在fd[3]上打开了文件,而进程B是在fd[4]上打开该文件, 接着向 fd[3] 中写入了一部分数据,在完成每个write后,当前文件偏移量即增加所写的字节数,fd[3] 的当前文件偏移量被更新。然后再向fd[4] 中写入数据时,由于此时fd[4] 的当前文件偏移量仍处在文件开头,所以写入的数据会覆盖了先前向 fd[3] 中写入的数据。

    如果用O_APPEND标志打开了一个文件,则该标志存储在file结构体中。每次执行写操作时,当前偏移量首先被设置为文件长度。

  2. 不同进程共享文件对象
    在这里插入图片描述
    进程间共享文件还有一种形式是共享文件对象。即共享file结构体。
    这附图就描述了不同进程共享文件对象的情况。
    在这幅图中,进程AB有各自的PCB和文件表。进程A的fd_array[3]和fd_array[4]指向了同一个文件对象,当然也就共享了索引节点。

    这种情况通常是调用fork函数之后,父子进程之间对文件的共享。

    由于文件偏移量存储在file对象的f_pos字段,所以两个进程共享。若a调用了lseek设置为50,进程b就要从50处开始读写。

3.2 进程内的文件共享

  1. 多次使用open函数打开相同文件

  2. 使用dup/dup2函数或者fcntl函数

dup函数

  1. 作用
    用于复制一个已存在的文件描述符,使新老文件描述符都只想同一个文件对象。

  2. 函数原型

    int dup( int filedes );
    
  3. 返回值
    成功返回新的(未占用的最小的)文件描述符
    出错返回-1

  4. 参数
    filedes:文件描述符

dup2函数

  1. 作用
    用于复制一个已经存在的文件描述符

  2. 函数原型

    int dup2(int filedes, int filedes2);
    
  3. dup和dup2的区别
    dup返回的新文件描述符一定是当前可用描述符中的最小值
    dup2则将文件描述符复制到指定位置,即将filedes复制到filedes2。
    如果filedes2已经打开,dup2则先将其关闭;若filedes2等于filedes,则直接返回filedes,而不关闭。

四. 其他重要I/O函数

4.1 sync/fsync/fdatasync函数

通常Linux实现在内核中设有缓冲区高速缓存或页面高速缓存。
大多数的磁盘I/O都通过缓冲区进行。
当将数据写入文件时,内核通常先将数据复制到某一个缓冲区中。
如果该缓冲区满或者内核需要重用该缓冲区,则将该缓冲排入到输出队列。
等到其达到队首时,才进行实际的磁盘读写操作。
延迟写。

延迟写是指把要写的电脑数据先都放到内存里,等积累多了再一次性写到硬盘,降低对硬盘的读写损耗。

延迟写优点
减少了磁盘读写次数
延迟写缺点
降低了文件内容的更新速度
当系统发生故障,高速缓冲区中的内容可能丢失
解决办法
对缓冲区进行清理,希望将数据写入到磁盘
sync、fsync、fdatasync起到了刷缓存的作用
  1. sync函数
    作用:

    将所有修改过的缓冲区排入写队列,然后就返回,并不等待实际的写磁盘操作结束。

    sync函数针对的是所有修改过的缓冲区,并不仅仅针对某个被修改过的文件

    通常称为update的系统守护进程会周期性地调用sync函数,即保证定期冲洗内核缓冲区

    原型:

    void sycn();
    
  2. fsync函数
    作用:
    fsync函数只对文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束后才返回。
    原型:

    int fsync(int filedes);
    

    参数:
    filedes:文件描述符

    返回值:
    成功返回0,出错返回-1

  3. fdatasync函数
    作用:
    fdatasync和fsync类似,但它只影响文件的数据部分;而fync不仅影响文件的数据,还同步更新文件的属性
    函数原型:

     int fdatasync(int filedes);
    

    参数:
    filedes:文件描述符

    返回值:
    成功返回0,出错返回-1。

  4. 对比
    在这里插入图片描述

4.2 fcntl函数

  1. 作用:
    用于改变已经打开文件的性质用于改变已经打开文件的性质

  2. 函数原型

    int fcntl(int filedes, int cmd, .../* int arg,可变参数 */)
  3. 返回值
    成功时,返回值依赖于第二个参数cmd
    出错时,返回-1

  4. 参数
    第三个参数依赖于第二个参数。

第一个参数filedes

已打开文件的文件描述符

第二个参数cmd的五种取值方式:

复制一个现存的描述符(cmd=F_DUPFD),fcntl返回新文件描述符。新描述符是尚未打开的各描述符中,大于或等于第三个参数值的最小值。

获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD),将文件描述符filedes对应的标志,作为返回值返回。

获得/设置文件状态标志(cmd=F_GETFL或F_SETFL),设置文件描述符filedes对应的标志。新标志按照第三个参数设置。文件状态标志包括:O_RDONLY、 O_WRONLY 、O_RDWR 、O_APPEND 、
O_NONBLOCK(非阻塞方式)、 O_SYNC(等待写方式)、O_ASYNC(异步方式,仅4.3+BSD)。

获得/设置异步I/O信号接收进程(cmd=F_GETOWN或F_SETOWN),将fcntl函数的第三个参数,设置为文件状态标志。可以更改的标志包括:O_APPEND、O_NONBLOCK、O_SYNC、O_ASYNC

获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW),获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。

4.3 ioctl函数

  1. I/O操作的杂物箱
  2. 其实现的功能往往和具体的设备有关系
  3. 设备可以自定义自己的ioctl命令
  4. 操作系统提供了通用的ioctl命令
  5. ioctl类似于windows的DeviceIoControl函数
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AWei_i_i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值