apue 第3章 文件I/O

引言

文件I/O的主要有五个常用的函数:

  • open
  • read
  • write
  • lseek
  • close

本章主要介绍不带缓存的I/O,所谓不带缓存I/O是指每一个调用都是内核的一个系统调用

文件描述符

对于内核而言,所有打开的文件都是通过文件描述符引用的。文件描述符0-标准输入,1-标准输出,2-标准错误。在POSIX规范中,已经提供了STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO来替代0、1、2数字,这样更加便于开发者理解。这些常量定义在<unistd.h>头文件中

函数open和openat

打开文件的函数如下:

#include <fctnl.h>
int open(const char* path, int oflag, ... /*mode_t mode*/)
int openat(int fd, const char* paht, int oflag, ... /*mode_t mode*/)
  • path 打开或创建的文件名字
  • oflag 打开的参数
    • O_RDONLY 只读打开
    • O_WRONY 只写打开
    • O_RDWR 读写打开
    • O_EXEC 只执行打开
    • O_SEARCH 只搜索打开
  • fd参数
    • paht为绝对路径时,相当于open
    • path为相对路径时,fd为相对路径名在文件系统中的开始地址
    • path指定相对路径名
#define O_RDONLY        0x0000          /* open for reading only */
#define O_WRONLY        0x0001          /* open for writing only */
#define O_RDWR          0x0002          /* open for reading and writing */
#define O_ACCMODE       0x0003          /* mask for above modes */

creat函数

创建一个文件,一般不使用,用open代替

#include<fcntl.h>
int creat(const char* path, mode_t mode);

相当于

open(paht, O_WRONY | O_CREATE | O_TRUNC, mode)

函数close

关闭一个打开的文件

#include<unistd.h>
int close(int fd);
  • 关闭一个文件会释放该进程加在该文件上的所有记录锁

函数lseek

当前文件偏移量,文件对写操作开始的位置。
正常非O_APPEND方式打开,偏移量会被重置为0。

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
  • whence 为SEEK_SET,文件偏移量为文件头开始的offset
  • whence 为SEEK_CUR,文件偏移量为当前值加上offset
  • whence 为SEEK_END,文件偏移量为文件长度加上offset

lseek可以形成文件空洞,实际上这个和内核实现无关,而是和文件系统相关,也就是说,允许空洞存在,如何存储空洞,都是归给文件系统的。目前大部分文件系统都是使用null填充。

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

char buf1[] = "abcdefghijk";
char buf2[] = "ABCDEFGHIJK";

int main()
{
    int fd;
    if ((fd=creat("file.hole", FILE_MODE)) < 0)
        err_sys("creat error");
    if (write(fd, buf1, 10) != 10)
        err_sys("buf1 write error");
    //offset now  = 10
    if (lseek(fd, 16384, SEEK_SET) == -1)
        err_sys("lseek error");
    //offset now = 16384
    if (write(fd, buf2, 10) != 10)
        err_sys("buf2 write error");
    //offset now=16394

    exit(0);

}

使用od命令查看

yanke@yanke-pc:~/yanke/Code/apue/ch3$ od -c file.hole
0000000   a   b   c   d   e   f   g   h   i   j  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0040000   A   B   C   D   E   F   G   H   I   J
0040012

例子:判断一个标准输入能不能设置偏移量

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
    if (lseek(STDIN_FILENO,0,SEEK_CUR) == 1)
        printf("cannot seek\n");
    else
        printf("seek OK\n");

    exit(0);
}
//$./a.out < /etc/passwd
//seek OK

函数read

从打开的文件读取数据

#include<unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes)
//返回值:读取到的字符数目,如果到达文件尾,返回0,如果出错,返回-1

函数wtrire

从打开的文件写入数据

#include<unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes);
//返回值:成功返回写入的字节数,出错返回-1

I/O效率

将标准输入复制到标准输出

#include "apue.h"
#include <unistd.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");
    }

    exit(0);
}
  • Linux的ext4文件系统,磁盘块的大小为4096字节(4K)
  • 大多数文件系统为了改善性能都使用了某种预读计数,当检测到正在顺序读取时,系统就试图读取比应用要求的更多数据。

文件共享

内核用于所用I/O的数据结构:内核使用3个数据结构表示打开的文件
1. 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表。包含文件描述符标志和指向文件表项的指针
2. 内核为所有打开文件维护一张文件表,每个文件表项包括:文件状态标志(读,写,添加,同步,阻塞等),当前文件偏移量,指向该文件的v节点。
3. 每个打开的文件有一个v节点,v节点包含文件类型和对此文件进行各种操作函数指针。对于大多数文件系统,v节点还包含i节点,这些i节点在文件打开时读入内存,包含文件的所有者,文件长度,指向文件实际数据快在磁盘所在位置的指针等。

如果两个独立的进程各自打开了同一个文件,则每个进程表指向自己的文件表项,文件表象指向相同的v节点。

原子操作

追加到一个文件

Unix提供一种原子操作,每次写操作前都要将进程的当前偏移量设置到该文件的尾端,于是每次写之前就不需要调用lseek了。

函数pread和pwrite
#include <unistd.h>
ssize_t pread(int fd, void* buf, size_t nbytes, off_t offset);
size_t pwrite(int fd, const void* buf, szie_t nbytes, off_t offset);
  • 使用pread时,无法中断其定位和读操作
  • 不更新当前文件偏移量

函数dup和dup2

用于复制一个文件:

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);

dup执行后将会返回最小的可返回的文件描述符,dup2则是自定义文件描述符值,如果fildes2正在使用则关闭后再分配;如果fildes等于fildes2则只返回fildes2,且不关闭。

数据同步到磁盘

void sync(void);
int fsync(int fd);
int fdatasync(int fd);
  • sync:将修改过的块缓冲区排入写队列然后返回
  • fsync:对fd有作用,等待磁盘写操作完成才返回
  • fdatasync:只影响文件的数据部分,FreeBSD系Unix实现不包含

文件控制函数

int fcntl(int fildes, int cmd, ...);
  1. 复制一个已存在的文件描述符(cmd=F_DUPFD、F_DUP、FD_CLOEXEC)
  2. 取得/设置已存在文件描述符的标志(cmd=F_GETFD、F_SETFD)
  3. 取得/设置已存在文件描述符的状态标志(cmd=GETFL、SETFL)
  4. 取得/设置异步I/O时文件的所有权(cmd=GETOWN、SETOWN+)
  5. 取得/设置异步锁(cmd=GETLK、SETLK、SETLKW)

cmd参数

  • F_DUPFD:复制一个已存在的文件描述符,新文件描述符作为函数的返回值,其取值一般是不小于3且还没被打开的文件描述符。新旧两个文件描述符共享文件表,但是两个描述符有不同的fd标志位(进程表中每一个表项记录了一个文件描述符的fd标志位和指向文件表的指针)。使用F_DUPFD时,fd标志位的FD_CLOEXEC将被清零。
  • F_DUPFD_CLOEXEC
    复制一个已存在的文件描述符,与F_DUPFD不同之处在于其设置了FD_CLOEXEC。也就是在使用exec是文件被关闭。
  • F_GETFD
    返回文件标志位,目前文件标志位只定义了FD_CLOEXEC,因此返回值就是FD_CLOEXEC的值。
  • F_SETFD
    设置文件标志位(即设置FD_CLOEXEC),设置的值在第三个参数。
  • F_GETFL
    返回该文件描述符对应的文件状态标志位file status flag。文件状态标志位主要有O_RDONLY、O_WRONLY,本文后面进一步讨论。
  • F_SETFL
    设置文件状态标志。只有O_APPEND、O_NONBLOCK、O_SYNC、O_DSYNC、O_RSYNC、O_FSYNC、O_ASYNC其中状态标志可以被更改(设置)。
  • F_GETOWN
    取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回的是负值(arg被忽略)
  • F_SETOWN
    设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程id和进程组id通过第三个参数arg传入(只能传一个,要么进程id,要么进程组id),arg为正值表示传入的是进程id,arg为负值则传入的是进程组id。
  • F_GETLK
    取得文件的锁定状态,如果被锁定了,则将锁定信息重写到第三个参数arg(一个指向flock的结构体的指针)。如果未被锁定状态,则除了struct flock的l_type被设置为F_UNLCK外,其他成员不变。
  • F_SETLK
    通过第三个参数arg(struct flock*)锁定文件。如果read lock和write lock设置失败,则返回EACCES or EAGAIN
  • F_SETLKW
    类似F_SETLK,不同的是当设置锁发生阻塞时,它会等待(W是wait的意思),直至设置锁完成

例子:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char* argv[])
{
    int val;
    if (argc != 2)
    {
        printf("usage: a.out < descriptor# > \n");
        exit(1);
    }
    if ((val=fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
    {
        printf("fcntl error for fd %d\n", atoi(argv[1]));
    }
    switch (val & O_ACCMODE)
    {
        case O_RDONLY:
            printf("read only");
            break;
        case O_WRONLY:
            printf("write only");
            break;
        case O_RDWR:
            printf("read write");
            break;
        default:
           {
               printf("unkonwn access mode");
               exit(1);
           }
    }


    if (val & O_APPEND)
        printf(", append");
    if (val & O_NONBLOCK)
        printf(", nonblocking");
    if (val & O_SYNC)
        printf(", synchronous writes");

    #if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
        if (val & O_FSYNC)
            printf(", synchronous writes");
    #endif
    putchar('\n');
    exit(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值