APUE学习笔记(三)文件IO

3.1 文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。

例子: 0表示stdin, 1表示stdout, 2表示stderr.

文件描述符的变化范围是0OPEN_MAX-1,早期允许每个进程打开19个文件。对很多系统来说,文件描述符范围是无限的。

3.2 open和openat

调用open或openat函数可以打开或创建一个文件。

 int open(const char *path, int oflag, ...);
 int openat(int fd, const char *path, int oflag, ...);

//如果path参数指定的是绝对路径名, 在这种情况下fd参数被忽略, openat函数就相当于open函数。
//path参数指定的是相对路径名,则openat要传入fd参数, fd参数是通过打开相对路径名所在的目录来获取。
//path参数指定了相对路径名, fd参数具有特殊值AT_FDCWD。 在这种情况下, 路径名在当前工作目录中获取。

3.3 create

基本不用,等价于

open(path, O_WRONLY| O_CREAT| O_TRUNC, mode) //O_WRONLY 只读打开,O_CREAT 不存在就创建,O_TRUNC 文件清空

3.4 lseek

每个打开文件都有一个偏移量,表示从文件开始处计算的字节数。读、写操作通常都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。lseek显式地为一个打开文件设置偏移量,如果设置失败返回-1。

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence)
/*whence指定偏移基准
  SEEK_SET   从开头
  SEEK_CUR  从当前
  SEEK_END  从结尾
  */

文件偏移量可以大于文件的当前长度,在这种情况下对该文件的下一次写将加长该文件,并在文件中构成一个空洞,空洞内容被置为0.

例子

#include "apue.h"
#include <fcntl.h>

char buf1[] = "abcdefg";
char buf2[] = "ABCDEFG";

int main(void) {
    char *file = "huahua3.txt";
    int fd;

    //以读写方式打开文件,若不存在则创建文件,文件权限wr
    if ((fd = open(file, O_RDWR | O_CREAT, 0600)) == -1)
        err_sys("open error");

    //向文件中写入数据长度10
    if (write(fd, buf1, 10) == -1)
        err_sys("write error");

    //将当前位置定位到16384
    lseek(fd, 16384, SEEK_SET);

    //向文件中写入数据
    if (write(fd, buf2, 10) == -1)
        err_sys("write error");

    exit(0);
}

验证空洞

ls -l huahua3.txt  //查看文件大小
od -c huahua3.txt //以字符格式打印内容

3.5 read

ssize_t read(int, void *, size_t) 

返回结果可能是

  • 读取成功,返回读字节数
  • 文件到达结尾,返回0
  • 读取失败,返回-1

3.6 write

ssize_t write(int __fd, const void *__buf, size_t __nbyte)

向文件写入指定大小数据,返回实际写入大小。

读写操作的例子

#include "apue.h"
#include <fcntl.h>

#define BUFFSIZE 128

int main(void) {
    char *file = "huahua.txt";
    char *file2 = "huahua2.txt";
    char *dir = "./";
    int fd;
    int dir_fd;

    char buf[BUFFSIZE];
    char *msg = "hello, huahua!";
    ssize_t read_bytes;

    //以读写方式打开文件,若不存在则创建文件,文件权限wr
    if ((fd = open(file, O_RDWR | O_CREAT, 0600)) == -1)
        err_sys("open error");

    //向文件中写入数据
    if (write(fd, msg, strlen(msg)) == -1)
        err_sys("write error");
    //将当前位置定位到开头
    lseek(fd, 0, SEEK_SET);
    //读取文件内容,写入控制台
    while ((read_bytes = read(fd, buf, strlen(msg))) > 0) {
        if (write(STDOUT_FILENO, buf, read_bytes) != read_bytes)
            err_sys("write error");
    }

    if (read_bytes < 0)
        err_sys("read error");

    //使用打开目录
    if ((dir_fd = open(dir, O_RDONLY)) == -1)
        err_sys("open error");
    //openat 打开目录中的文件
    int fd2 = openat(dir_fd, file2, O_RDWR|O_CREAT, 0600);
    if (fd2 == -1)
        err_sys("openat error");

    //向文件中写入数据
    if (write(fd2, msg, strlen(msg)) == -1)
        err_sys("write error");
    //将当前位置定位到开头
    lseek(fd2, 0, SEEK_SET);
    //读取文件内容,写入控制台
    while ((read_bytes = read(fd2, buf, strlen(msg))) > 0) {
        if (write(STDOUT_FILENO, buf, read_bytes) != read_bytes)
            err_sys("write error");
    }

    exit(0);
}

3.7 IO效率

缓冲区的大小影响IO效率,当缓冲区达到磁盘块大小时,系统CPU时间最小;由于文件的预读取机制,缓冲区达到32字节之后,进行顺序读取的时钟时间都相差不大。

3.8 文件相关数据结构

内核使用三种数据结构描述一个打开的文件

  • 每个进程在进程表中都有一个记录项, 记录项中包含一张打开文件描述符表(文件描述符标志,文件表指针)。
  • 内核为所有打开文件维持一张文件表(文件状态标志,偏移量,v节点指针)。
  • 每个打开文件或设备都有一个 v 节点结构(文件的具体信息)。
打开该文件的每个进程都获得各自的一个文件表项, 但对一个给定的文件只有一个v节点表项。每个进程维护自己的偏移量。
可能有多个文件描述符指向同一个文件表项,就是多个进程共享一个文件。

3.9 原子操作

由于通常每个进程维护自己的偏移量,定位+读写,两个动作经常需要保持原子执行,否则在多进程下会带来意外结果。

APPEND,在打开文件时指定,能够在每次写的时候自动定位到结尾,而不需要显式调用lseek

pread,pwrite,在执行读取时指定位置并且不会修改文件偏移量。

ssize_t pread(int d, void *buf, size_t nbyte, off_t offset);

3.10 复制文件描述符

dup(fd)   //生成一个文件描述符和fd指向同一个文件表项,返回的一定是进程未使用的最小的文件描述符
dup2(fd1, fd2)  //使fd2指向fd1, 返回文件描述符和fd1指向同一个文件表项。如果fd1和fd2相等,直接返回fd2; 如果fd2是打开的文件,则先关闭fd2

例子

#include "apue.h"

int main(void) {
    FILE *fp;
    int fd;
  //生成fd,指向stdout
    fd = dup(fileno(stdout));

    printf("该文本重定向到 stdout\n");
    fflush(stdout);

   //重新打开stdout, 关联到file.txt,原来的stdout被关闭了
    fp = freopen("file.txt", "a", stdout);
    printf("该文本重定向到 file.txt\n");
    fflush(fp);

   //把新的stdout指向保存的旧stdout,重定向回去
    dup2(fd, fileno(stdout));
    printf("该文本重定向到 stdout\n");

    exit(0);
}

3.11 刷新缓存

当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式被称为延迟写.

sync, fsync, fdatasync用来通知系统进行缓存刷新,把缓存中的数据写入磁盘。

3.12 fcntl和ioctl

内容太杂乱了,先空着。

fcntl函数可以改变已经打开文件的属性。

ioctl函数一直是I/O操作的杂物箱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值