《apue》读书笔记 第三章 文件I/O

第三章 文件I/O

1.引言

Unix系统中大多数文件I/O只需用到五个函数:open、read、write、lseek、close。本章介绍的I/O是不带缓冲的,即:每个read和write都调用内核中的一个系统调用。它们不是ISO C的组成部分。

2.文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用;

当打开或创建文件时,内核向进程返回一个文件描述符;

读写文件时,文件描述符将作为read和write的参数。

在 unistd.h中定义三个标准的文件描述符:

STDIN_FILENO (0)  标准输入

STDOUT_FILENO (1) 标准输出

STDERR_FILENO (2) 标准出错输出

文件描述符的变化范围是0~OPEN_MAX, 大多数系统的OPEN_MAX为63。

3.函数

具体函数描述:在 fcntl.h 头文件下。

3.1 open函数

int open(const char *path, int oflag, ... );  //打开或者创建一个文件

oflag参数可用来说明此函数的多个选项,一个或多个常量通过“或”运算构成oflag。

必须的参数: O_RDONLY, O_WRONLY, O_RDWR,这三个必须选一个。

此外还有其他可选的参数,这里不一一列举。

3.2 creat函数

int creat(const char *path, mode_t mode); //创建一个文件

3.3 close函数

int close(int fildes);//关闭一个打开的文件

3.4 lseek函数

off_t lseek(int fildes, off_t offset, int whence);  //为打开的文件设置偏移量

whence可选:SEEK_SET, SEEK_CUR, SEEK_CUR,分别表示绝对偏移量,相对于当前位置的偏移量,相对文件尾端的偏移量。

通常偏移量是一个非负整数,但是对有些设备来说可以是负的,所以为安全起见,对返回值不要测试它是否小于0,而要测试它是否等于-1。

当文件偏移量大于文件的长度时,对文件的下一次写将加长该文件,并在文件中构成一个空洞(没有写过的字节被读为0),文件的空洞并不占用磁盘的存储区。

3.5 read函数

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

返回值:若成功则返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1。

3.6 write函数

ssize_t write(int fildes, const void *buf, size_t nbyte);

返回值:若成功则返回已写的字节数,若出错则返回-1。

3.7 I/O的效率

系统一般会采用预读技术以改善性能(实际读入的数据比应用程序要求的多)。

一个综合的例子:

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

int main()
{
    char *filename = ".//file";
    char buf[100];
    int fd;
    memset(buf,0,100);
    printf("Open file to write\n");
    //打开文件,不存在则新建
    if((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
    {
        perror("Cannot open file\n");
        exit(1);
    }
    printf("Open file successfully.\n");
    printf("Input a string: ");
    gets(buf);
   //写入文件
    write(fd,buf,strlen(buf));
    close(fd);
    printf("Open file to read.\n");
     if((fd = open(filename, O_RDONLY)) == -1)
    {
        perror("Cannot open file\n");
        exit(1);
    }
    //从文件中读取
    read(fd,buf,100);
    printf("Read from file is: %s\n",buf);
    close(fd);
    return 0;
}

4. 文件共享

打开文件的内核数据结构:


《apue》读书笔记 第三章 文件I/O_第1张图片

×linux没有使用v节点,而是使用了通用的i节点结构。

两个独立进程各自打开同一个文件:


《apue》读书笔记 第三章 文件I/O_第2张图片

每个进程都有自己的文件表项的一个理由是:使得每个进程都有它自己的对该文件的当前偏移量。

×原子操作:由于文件的共享,所以操作要考虑原子性。

pread相当于lseek 和 read, 但是调用pread时,无法中断其定位和读操作,它具有原子性,同样的有pwrite。

原子:要么全部执行成功,要么全都不执行。

5. 复制一个现存的文件描述符

#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
//返回值:成功则返回新的文件描述符,出错则返回-1。

dup返回的新文件描述符是当前可用文件描述符中的最小值,dup2则是用fildes2参数指定新文件描述符的数值,若fildes2打开则先将其关闭,若fildes等于fildes2则不关闭,返回fildes2。在CGI程序用dup2将文件描述符重新定位到标准输出和标准输入。即:dup2(fd,STDOUT_FILENO)或dup2(fd,STDIN_FILENO)。

另一个方法:使用fcntl函数:

dup(filedes) 等效于 fcntl(filedes, F_DUPFD, 0);

dup(filedes, filedes2) 等效于 close(filedes2); fcntl(filedes, F_DUPFD, filedes2);

6. 改变已打开文件的性质函数fcntl

int fcntl(int fd, int cmd, ... /* arg */ );

函数功能:
复制一个现有的描述符(cmd=F_DUPFD)
获得或设置文件描述符(cmd=F_GETFD|F_SETFD)
获得或设置文件状态标志(cmd=F_GETFL|F_SETFL)
获得或设置异步I/O所有权(cmd=F_GETOWN|F_SETOWN)
获得或设置记录锁(cmd=F_GETLK|F_SETLK、F_SETLKW)

可以用fcntl函数设置文件状态,常用设置套接字描述符为非阻塞O_NONBLOCK。

下面写个程序完成打印出指定文件描述符的文件状态标志:

#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)
        err_sys("fcntl error for fd %d", 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:
        err_dump("unknown access mode");
    }

    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);
}

测试结果:

$ ./a.out 0 < /dev/tty
read only
$ ./a.out 1 > temp.foo
$ cat temp.foo
write only
$ ./a.out 2 2>>temp.foo
write only, append
$ ./a.out 5 5<>temp.foo
read write

$ ./a.out 0 < /dev/tty 理解:用tty设备,取代文件0(stdin),来运行a.out,也就是说a.out的所有需要输入的信息,都是通过tty设备获得的,而不是用户终端(有可能用户终端也是tty)。

$ ./a.out 1 > temp.foo 理解:将输出重定位到temp.foo中。

$ ./a.out 2 2>>temp.foo 理解:标准错误以追加的形式输出到temp.foo中。

$ ./a.out 5 5<>temp.foo 理解:在文件描述符5上打开文件temp.foo以供读和写。

7. sync,fsync和fdatasync函数

大多数磁盘I/O都通过缓冲进行,比如延迟写(缓冲区写满时才写)。

延迟写减少了磁盘读写次数,但是降低了文件内容的更新速度,可能因系统故障而产生数据不一致。

#include <unistd.h>

int fsync(int filedes);

int fdatasync(int filedes);

void sync(void);

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

通常守护进程update每隔30秒就会调用sync函数。

fsync只对由文件描述符filedes指定的单一文件起作用,并等待磁盘操作结束才返回。

fdatasync类似与fsync,但是只影响文件的数据部分,而fsync还会同步更新文件的属性。

8. ioctl函数

ioctl函数是I/O操作的杂物箱,较多用于终端I/O。

9. /dev/fd

/dev/fd的目录项是名为0,1,2等的文件,打开文件/dev/fd/n等效于复制描述符n(前提是描述符n是打开的)。

在cat(1)程序中,它将单独的一个字符’-‘解释为标准输入,如:

filter file2 | cat file1 - file3 | lpr

cat先读file1,再读标准输入(及filter file2的输出),然后读file3。

上述命令等价于:

filter file2 | cat file1 /dev/fd/0 file3 | lpr

这样子可以提高文件名参数的一致性,更加清晰。 因为使用‘-’看起来像是一个选项。

你可能感兴趣的:(unix,apue,文件I-O)