第三章-文件I/O

引言

本章描述的函数经常被称为不带缓冲的I/O,先说明可用的文件I/O函数,然后,进一步讨论在多个进程间如何共享文件,以及所涉及的内核有关数据结构,最后,将说明dup、fcntl、sync、fsync和ioctl函数。

文件描述符

内核通过文件描述符引用打开的文件,并且规定,文件描述符0预留给标准输入,1预留给标准输出,2预留给标准错误,为了提高可读性,习惯用符号常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO表示,文件描述符的变化范围是0~OPEN_MAX-1,允许每个进程最多打开的文件数量为63。

函数open、openat和create

用函数open、openat打开或可能创建一个文件,函数create创建一个新文件

  #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);

       int creat(const char *pathname, mode_t mode);

       int openat(int dirfd, const char *pathname, int flags);
       int openat(int dirfd, const char *pathname, int flags, mode_t mode);

1. pathname是要打开或创建 文件的名字。
文件名和路径名截断
以下示例演示用pathconf函数到底是截断过长的文件名还是出错

 #include <limits.h>
 #include <unistd.h>
 #include <stdio.h>

#define PATH "/home/apue.3e/fileio"
char *info[_POSIX_NAME_MAX] = {
    "name is trunc!",
    "generates an error!",
    NULL
};
 int main(void)
 {
     long int filenameLimit = pathconf(PATH,_PC_NAME_MAX);
     long int pathLimit = pathconf(PATH,_PC_PATH_MAX);
     long int trucFlag = pathconf(PATH,_PC_NO_TRUNC);
     printf("the maximum length of a filename in the directory:%ld\n"
            "the maximum length of a relative pathname:%ld\n"
            "trucFlag:%s\n", filenameLimit,pathLimit,(trucFlag==0) ? info[0]:info[1]);
     return 0;
 }

运行该示例,结果如下:
在这里插入图片描述
在我的linux系统环境下面,当文件名过长时候,目录行为是“产生错误”,

2. open和openat函数返回的文件描述符一定是最小的未用的值

3. flags参数指定文件访问模式 :O_RDONLY,O_WRONLY, or O_RDWR

4. creat函数创建一个新文件 (以只写方式)

函数close

close函数关闭一个文件,会释放该进程加在该文件上的所有记录锁。一个进程终止时,内核自动关闭它所有打开的文件。

函数lseek

  1. 当前文件偏移量,通常是一个非负值,给出读写文件的位置,也有为负值的例外
  2. lseek函数显式的为一个打开文件设置偏移量
  3. lseek函数返回新的文件偏移量,对于网络套接字,管道或FIFO而言,返回-1,不支持设置偏移量
    以下示例是测试标准输入是否可设置偏移量
#include "apue.h"
int
main(void)
{
        if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
                printf("cannot seek\n");
        else
                printf("seek OK\n");
        exit(0);
}

运行结果
在这里插入图片描述
以下示例程序创建一个具有空洞的文件

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

char    buf1[] = "abcdefghij";
char    buf2[] = "ABCDEFGHIJ";

int
main(void)
{
        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);
}

运行该示例,其结果如下:
在这里插入图片描述
文件中间的30个未写入字节都被读成0

函数read

从打开文件中读数据

#include <unistd.h>
       ssize_t read(int fd, void *buf, size_t count);

count参数 指定想要从打开文件中读多少字节数据,read函数返回实际从文件中读到的字节数据,实际读到的数据一定是不大于count指定值的。出错的时候,read函数返回-1.

函数write

向打开文件写数据

 ssize_t write(int fd, const void *buf, size_t count);

返回值通常和count相同
以下是一个读写文件的测试程序,仅供参考!

 void test_RDWR(void)
 {
     char buf[BUF_SIZE]={};
     ssize_t ret = 0;
     int fd = 0;
     fd = open(FILE_NAME, O_RDWR);
     if (-1 == fd)
     {
         perror("open errors!");
         exit(0);
     }
     ret = read(fd, buf, BUF_SIZE-1);
     if (-1 == ret)
     {
         perror("read errors!");
         exit(0);
     }
      printf(" the  number of bytes read:%d\n"
             "bytes read:%s\n",ret,buf);

     ret =  write(fd, buf, BUF_SIZE - (ssize_t)1);
     if (-1 == ret)
     {
         perror("write errors!");
         exit(0);
     }
      printf(" the  number of bytes read:%d\n",ret);
      fclose(fd);
 }

I/O的效率

文件共享

Unix系统支持在不同进程间共享打开文件,其内核用3种数据结构表示打开文件

  1. 进程表项包含一张打开文件描述符表,其成员文件描述符标志和指向一个文件表项的指针
  2. 文件表项,其成员是文件状态标志,当前文件偏移量和指向该文件V节点表项的指针
  3. V节点表项,其成员是文件类型,对此文件进行各种操作函数的指针和该文件的i节点
    下图表示了这三种数据结构的关系
    在这里插入图片描述
    两个独立进程各自打开同一文件的情形
    在这里插入图片描述
    从上图可以看出,对一个给定的文件只有一个v节点表项,而每个进程都获得各自的一个文件表项,这样可以使得每个进程都有它自己对该文件的当前偏移量。所以当多个进程写同一文件时,可能会产生预想不到的结果,为了避免这种结果,我们需要考虑到原子操作。
    多个文件描述符项指向同一文件表项的情形,如dup函数,fork函数等。

原子操作

原子操作指的是由多步组成的一个操作。

  1. 追加到一个文件
    考虑两个独立的进程A和B都对同一文件进行追加写操作的情形,操作是分两步(先定位到文件尾端,然后写),如果进程A先定位到文件尾端,内核切换到进程B,进程B定位到文件尾端,完成写后返回到进程A,进程A写就会覆盖先前进程B写的数据。这便产生了不是我们预料的结果。
  2. 函数pread和pwrite
    这两个函数实现了原子地定位并执行I/O
   #include <unistd.h>
       ssize_t pread(int fd, void *buf, size_t count, off_t offset);
       ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
  1. 创建一个文件

函数dup和dup2

赋值一个现有的文件描述符

#include <unistd.h>

       int dup(int oldfd);//返回的一定是当前可用文件描述符中的最小数值
       int dup2(int oldfd, int newfd);//newfd指定了新文件描述符的值

执行dup(1)后的内核数据结构
在这里插入图片描述
我们看到了两个文件描述符指向同一文件表项。
f’cntl函数也能复制一个文件描述符,有如下等效式
dup(fd) 等同于 fcntl(fd, F_DUPFD, 0)
dup2(fd,fd2)等同于close(fd2) fcntl(fd, F_DUPFD, fd2)但是dup2()是原子操作。

函数sync、fsync和fdatasync

这几个函数保证磁盘上实际文件系统和缓冲区内容的一致性

 #include <unistd.h>
       void sync(void);//不等实际写磁盘操作结束
       int fsync(int fd);//会等实际写磁盘操作结束
       int fdatasync(int fd);//只影响文件的数据部分

函数fcntl

可改变已经打开文件的属性,

#include <unistd.h>
       #include <fcntl.h>
       int fcntl(int fd, int cmd, ... /* arg */ );

以下示例把第一个参数指定为文件描述符,并对该描述符打印其所选择的文件标志说明

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

int
main(int argc, char *argv[])
{
        int             val;

        if (argc != 2)
                err_quit("usage: a.out <descriptor#>");

        if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)//F_GETFL 读文件描述符标志。
                err_sys("fcntl error for fd %d", atoi(argv[1]));

        switch (val & O_ACCMODE) { //O_ACCMODE取出文件状态标志的前两位
        case O_RDONLY:
                printf("read only");
                break;

        case O_WRONLY:
                printf("write only");
                break;

        case O_RDWR:
                printf("read write");
                break;

        default:
                err_dump("unknown access mode");
        }

        if (val & O_APPEND)	//在每次写之前,都将标志位移动到文件的末端
                printf(", append");
        if (val & O_NONBLOCK)  //人为 的设置读写操作为非阻塞方式
                printf(", nonblocking");
        if (val & O_SYNC)  //每次write系统调用后都等待实际的物理I/O完成
                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);
}

运行该程序结果如下:
在这里插入图片描述
5<>temp.foo表示在文件描述符5上打开以供读写
/dev/tty 表示当前控制台,就是当前进程控制台的设备文件,输入命令“tty”可查看当前映射终端,
以下示例是对一个文件描述符设置一个或多个文件状态标志,

#include "apue.h"
#include <fcntl.h>
void
set_fl(int fd, int flags) /* flags are file status flags to turn on */
{
        int             val;
        if ((val = fcntl(fd, F_GETFL, 0)) < 0)
                err_sys("fcntl F_GETFL error");
        val |= flags;           /* turn on flags */
        if (fcntl(fd, F_SETFL, val) < 0)
                err_sys("fcntl F_SETFL error");
}

调用set_fl(STDOUT_FILENO, O_SYNC)就能开启同步写标志,实现write等待数据已经写到磁盘上再返回。

函数ioctl

该函数实现其他I/O操作,每个设备驱动程序可定义它自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令。

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

/dev/fd

打卡文件“/dev/fd/n” 等效于复制描述符n

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值