【Unix】分章复习笔记之第三章 · 文件IO (2)

【Unix】分章复习笔记之第三章 · 文件IO (2)

3.8 IO效率相关的问题

3.8.1 课本的示例代码的详细理解
#include "apue.h"
#include "myerr.h"
#define BUFFSIZE 4096

int main()
{
    int n;
    char buf[BUFFSIZE];
    while((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
    if(write(STDOUT_FILENO,buf,n)!=n)
        err_sys("write error");

    if (n<0)
    err_sys("read error");
    return 0;
}

在这里插入图片描述

此段代码目的是利用read和write函数通过标准输入输出的方式将文件复制。==注意,既然是标准输入输出,那么,运行此功能应使用重定向的方式否则,运行时会卡住别问我为什么知道。==为什么选择使用的BUFFZSIZE是4096?

测试所用的文件系统是linux ext4文件系统,其磁盘块的长度为4096字节。再增大缓冲区长度对时间基本无影响。

3.8.2 注意事项
  • 所有常用的UNIX系统shell都提供一种方法:在标准输入上打开一个文件用于读,在标准输出上创建或重写一个文件。这样程序可以不必打开输入输出文件(此代码中就无open函数)且允许使用重定向。
  • 进程终止时,UNIX的内核自动关闭进程的所有打开文件的文件描述符,因此不需要使用close函数
  • 文本文件或二进制文件都可以使用本程序,因为对于UNIX内核而言,两者无区别

3.9 内核I/O的数据结构

3.9.1 进程表项

每个进程在进程表里都有一个记录表项,包含一张打开文件描述符表。每一项包含两个部分:

  • 文件描述符标志
  • 指向一个文件表项的指针

我们通常这样描述进程表项:

在这里插入图片描述

3.9.2 文件表项

内核为所有打开文件维持一张文件表。包含:

  • 文件状态标志

  • 当前文件偏移量

  • 指向该文件v节点表项的指针

在这里插入图片描述

3.9.3 v节点结构

每个打开的文件设备都有一个v节点结构。v节点包含了

  • 文件类型和对此文件进行各种操作的指针。(一般统一书写为v节点信息)
  • 该文件的i节点(在v_data内包含)

i节点包含了:

  • 文件所有者,文件长度,指向文件实际数据块在磁盘上的所在位置(一般写为i节点信息)
  • i_vnode

一般我们这样表示:

在这里插入图片描述

注意:Linux没有使用v节点,而是使用了通用i节点的结构。虽然实现方式不同,但是在概念上,v节点与i节点是一样的,都指向文件系统特有的i节点结构

  • 不同的进程打开啊同一个文件时,fd标志可能不同,文件表项各自使用各自的。因为每个进程都可能有它自己对统一文件的当前文件偏移量。
  • 每次write后,文件表项中的当前文件偏移量增加nbyte字节,即写入的字节数 。如果导致写入后的文件偏移量大于原文件长度,则i节点的当前文件长度更新为当前文件偏移量。
  • 当用O_APPEND参数打开文件,文件表项中的文件状态标志也会随之变为O_APPEND,即执行写操作时,文件表项的当前文件偏移量先被设置为i节点的当前文件长度。
  • lseek函数只修改当前问价偏移量,不会进行任何IO操作。

3.10 原子操作

3.10.1 多线程同时写问题

假设现存 两个进程,两个进程打开同一文件且没有使用O_APPEND标志(早期的UNIX没有此标志),则两者可能使用同一段代码:

if(lseek(fd,0,SEEK_END)<0)
    err_sys("error");
if(write(fd,buf,100)!=100)
    err_sys("error");

代码意为先定义到文件末尾,再写1600字节。假设文件长度为1500字节。现在有AB两个进程都干这事:

时间节点AB
1调用lseek函数使得A进程的文件偏移量为1500
2调用lseek函数使得B进程的文件偏移量为1500
3调用write函数,写100字节,则写后文件偏移量增加到1600
4调用write函数,写100字节,把B进程写入的100字节覆盖

出现的问题在于:逻辑上的操作为:先定位到文件末尾,再写。然而当两个步骤进行的中间发生了进程切换(A在时间节点1后切换到了B进程),就可能会出现上面的错误。

为了解决上述错误,O_APPEND参数提供了一种原子操作,使得打开文件后写操作一定出现在文件末尾。因此不必再用lseek定位。

3.10.2函数pread和pwrite
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
					若成功,返回读到的字节数。如果在末尾,返回0;否则,返回-1
ssize_t pwrite(itn fd, void *buf, size_t nbytes, off_t offset);
									若成功,返回已写的字节数;否则返回-1

首先,我们发现了一个收悉的面孔:offset重现江湖。上一次见到他是在lseek,表明设置的偏移量。这里也差不多:

offset:读取的起始地址的偏移量,读取地址=文件开始+offset。注意,执行后,文件偏移指针不变

调用pread并不相当于先lseek再read。因为,它们二者之间有重要差别:

  • 调用pread时,无法中断其定位和读操作
  • 不更新当前文件偏移量

pwrite同上

3.10.3 原子操作定义

原子操作指的是由多步组成的一个操作。如果该操作原子地执行,则要么不执行,要么执行到底。

3.11 函数dup和dup2

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
									成功,返回新的文件描述符;否则返回-1
3.11.1 dup和dup2有啥区别

dup和dup2都是为了复制一个现有的文件描述符。

  • dup函数只传入了一个文件描述符fd。它的返回值也是一个文件描述符。这个文件描述符是该进程表项里面所有文件描述符的最小值。
  • dup2可以用fd2的参数值指定新的文件描述符的值。如果fd2已经打开,则先将其关闭。如果fd=fd2,则dup2返回fd2而不关闭它。这样fd2在进程调用exec时是打开状态。
  • 这些函数返回的新的文件描述符与参数fd共享同一个文件表项。

注意,dup2函数是原子操作

3.11.2 代码示例
#include "apue.h"
#include <unistd.h>
#include <fcntl.h>

int main()
{
    int fd ;
    fd = open("/home/ubuntu/workspqce/TestText.txt",O_RDWR);
    int newfd = dup(fd);
    int newfd2 = dup2(fd,3);
    printf("%d,%d\n",newfd,newfd2);
    return 0;
}

在这里插入图片描述

3.11.3 看图

在这里插入图片描述

这里先假设3暂时没被占用(因为正常情况下,0,1,2都被占用了。还记得吗?shell打开了012并依次作为标准输入,标准输出,标准错误) 这时,执行dup(1),可以发现,fd1和fd3同时指向同一个文件表项。

3.12 函数sync,fsync,fdatasync

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
														成功,返回0;否则,返回-1
void sync(void);
  • sync将所有修改过的块缓冲区排入写队列,然后返回。它并不等待实际写磁盘操作结束。系统守护进程会周期性的调用sync。

  • fsync对fd指定的文件起作用,且等写入磁盘操作结束后返回。它不仅 写入数据,还修改文件的属性。

  • fdatasync类似fsync,但不修改文件属性。

个人认为不是很重要

3.13 函数fcnt1

#include <fcnt1.h>
int fcnt1(int fd, int cmd, ...);
														若成功,依赖于cmd;失败返回-1

第三个参数可以是一个整数,也可以是一个结构指针。主要有以下5个功能:

  • 复制已有的文件描述符:F_DUPFD或F_DUPFD_CLOEXEC
  • 获取/设置文件描述符:F_GETFD或F_SETFD
  • 获取/设置文件状态标志:F_GETFL或F_SETFL
  • 获取/设置异步I/O所有权:F_GETOWN或F_SETOWN
  • 获取/设置记录锁:F_GETLK、F_SETLK或F_SETLKW

fcntl也可复制已有的文件描述符

‒ 调用dup(fd)等价于fcntl(fd, F_DUPFD, 0)

**‒ 调用dup2(fd, fd2)等价于先调用close(fd2)再调用fcntl(fd, F_DUPFD, fd2) **

用的不多??不清楚,感觉不是很容易考~~

3.14 /dev/fd

比较新的系统都会提供一个特殊的/dev/fd目录。其中的目录项为0,1,2…的文件。

打开文件/dev/fd/n 相当于复制文件描述符n。(前提是文件描述符n打开)。意味着进行如下两个操作,得到的结果没有区别:

fd = open("/dev/fd/0",mode);
fd = dup(0);

代码证明:

#include "apue.h"
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd = open("/dev/fd/0", O_RDWR);
    int fd1 = dup(0);
    printf("fd = %d , fd1 = %d\n",fd,fd1);
    return 0;
}

在这里插入图片描述

纯手码,很辛苦的。希望考试没事

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值