原子操作

UNIX/Linux系统编程手册第5章深入探究文件I/O,所有的系统调用都是以原子操作方式执行。
内核保证系统调用中的所有步骤会作为一个独立操作而一次性加以执行,期间不会为其它进程或线程中断.
下面的代码中open并未使用O_EXCL标志,在程序中为了对执行该程序的进程加以区分,打印了进程号。

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2015.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 5-1 */

/* bad_exclusive_open.c

   The following code shows why we need the open() O_EXCL flag.

   This program tries ensure that it is the one that creates the file
   named in its command-line argument. It does this by trying to open()
   the filename once without the O_CREAT flag (if this open() succeeds
   then the program know it is not the creator of the file), and if
   that open() fails, it calls open() a second time, with the O_CREAT flag.

   If the first open() fails, the program assumes that it is the creator
   of the file. However this may not be true: some other process may have
   created the file between the two calls to open().
*/
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"

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

    if (argc < 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s file\n", argv[0]);

    fd = open(argv[1], O_WRONLY);       /* Open 1: check if file exists */
    if (fd != -1) {                     /* Open succeeded */
        printf("[PID %ld] File \"%s\" already exists\n",
                (long) getpid(), argv[1]);
        close(fd);
    } else {
        if (errno != ENOENT) {          /* Failed for unexpected reason */
            errExit("open");
        } else {
            printf("[PID %ld] File \"%s\" doesn't exist yet\n",
                    (long) getpid(), argv[1]);
            if (argc > 2) {             /* Delay between check and create */
                sleep(5);               /* Suspend execution for 5 seconds */
                printf("[PID %ld] Done sleeping\n", (long) getpid());
            }
            fd = open(argv[1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
            if (fd == -1)
                errExit("open");

            printf("[PID %ld] Created file \"%s\" exclusively\n",
                    (long) getpid(), argv[1]);          /* MAY NOT BE TRUE! */
        }
    }

    exit(EXIT_SUCCESS);
}

执行结果:

书中图5.1很清晰的表达了意思,代码中我们设置如果argc>2,就是输入的参数大于2的话会sleep 5秒,然后&的作用是让指令进入后台。
在第一条指令中orginal process只是为了凑数,让第一个调用bad_exclusive_open的进程sleep5秒,如果不加&,程序会同步等待5秒,然后再去执行,默认情况下,进程是在前台运行的,这时就把shell给占据了,我们无法进行其它操作。对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加一个'&'实现这个目的。

fcntl系统调用

fcntl系统调用对一个打开的文件描述符执行一系列控制操作。针对一个打开的文件,获取或修改其访问模式和状态标志。
例子:添加O_APPEND标志:

int flags;
flags = fcntl(fd, F_GETFL);  /*获取当前标志的副本*/
if(flags == -1) 
     errExit("fcntl");
flags |= O_APPEND;     /*添加标志*/
if(fcntl(fd,F_SETFL,flags) == -1) 
    errExit("fcntl");  /*更新状态*

文件描述符

首先明确文件描述符和打开的文件之间并非一一对应的关系。多个文件描述符指向同一打开文件,既有可能,也有必要。
要理解具体情况要理解三个结构:

进程级的文件描述符表

进程级的文件描述符表(每个条目包含:控制文件描述符操作–close-on-exec标志 and 对打开的文件句柄的引用)

系统级的打开文件表

系统级的打开文件表(每个条目包含:当前文件偏移量 and 打开文件时使用的状态 and 文件访问权限-一般为创建文件时设置 and 与信号驱动I/O相关的设置 and 对该文件 i-node 对象的引用)

文件系统的i-node表

文件系统的i-node表(每个条目包含:文件类型和访问权限 and 一个指向该文件所持有的锁的列表的指针 and 文件各种属性)访问一个文件时,会在内存中为i-node创建一个副本。
文件描述符 打开的文件句柄 i-node之间的关系

在文件特定偏移量处的I/O

系统调用pread()和pwrite()完成与read()和write()相同的工作,只是前两者会在offset参数所指定的位置进行I/O操作,而非始于文件的当前偏移量处,且不会改变文件的偏移量.

分散输入和分散输出 readv() writev()

这些系统调用并非只对单个缓冲区进行读写操作,而是一次即可传输多个缓冲区的数据。

#include <sys/uio.h>

struct iovec {
    void *iov_base;   /*缓冲区的开始位置*/
    size_t iov_len;   /*缓冲区大小*/
};
/*从文件中读取一片连续的字节,然后将其散置于 iov 指定的缓冲区中*/
ssize_t readv(int fd,const struct iovec *iov, int iovcnt);

/*将 iov 指定的所有缓冲区中的数据拼接起来,然后以连续的字节序列写入文件*/
ssize_t writev(int fd, struct iovec *iov, int iovcnt);

下面的代码演示了readv()的用法:

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2015.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 5-2 */

/* t_readv.c

   Demonstrate the use of the readv() system call to perform "gather I/O".

   (This program is merely intended to provide a code snippet for the book;
   unless you construct a suitably formatted input file, it can't be
   usefully executed.)
*/
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    int fd;
    struct iovec iov[3];
    struct stat myStruct;       /* First buffer */
    int x;                      /* Second buffer */
#define STR_SIZE 100
    char str[STR_SIZE];         /* Third buffer */
    ssize_t numRead, totRequired;

    if (argc != 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s file\n", argv[0]);

    fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        errExit("open");

    totRequired = 0;

    iov[0].iov_base = &myStruct;
    iov[0].iov_len = sizeof(struct stat);
    totRequired += iov[0].iov_len;

    iov[1].iov_base = &x;
    iov[1].iov_len = sizeof(x);
    totRequired += iov[1].iov_len;

    iov[2].iov_base = str;
    iov[2].iov_len = STR_SIZE;
    totRequired += iov[2].iov_len;

    numRead = readv(fd, iov, 3);
    if (numRead == -1)
        errExit("readv");

    if (numRead < totRequired)
        printf("Read fewer bytes than requested\n");

    printf("total bytes requested: %ld; bytes read: %ld\n",
            (long) totRequired, (long) numRead);
    exit(EXIT_SUCCESS);
}

截断文件

int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
若文件长度大于参数 length,调用将丢弃超出部分,若小于参数 length,调用将在文件尾部添加一系列空字节或者一个文件空洞。区别在于一个通过路径打开,且对文件拥有可写权限,另一个通过描述符打开,此描述符必须有可写权限。

大文件I/O

通常我们用来存放文件偏移量的 off_t 为有符号的长整型,在32位机中,这将文件大小置于 2^31 -1 字节之下,(同理64位的理论上范围达到 2^63-1,基本已经超出磁盘容量)所以对文件的长度有限制。但有时候会有大文件然后长整型范围已经表示不了的情况。

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2015.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* Listing 5-3 */

/* large_file.c

   Demonstrate the use of the (obsolete) Large File System API.

   This program is Linux-specific.
*/
#define _LARGEFILE64_SOURCE
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    int fd;
    off64_t off;

    if (argc != 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s pathname offset\n", argv[0]);

    fd = open64(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1)
        errExit("open64");

    off = atoll(argv[2]);
    if (lseek64(fd, off, SEEK_SET) == -1)
        errExit("lseek64");

    if (write(fd, "test", 4) == -1)
        errExit("write");
    exit(EXIT_SUCCESS);
}

执行改程序输入./large_file x 10111222333
ls -l x会看到该文件的大小为10111222333
代码相对之前的代码比较简单,打开指定的文件,检索至给定的文件偏移处(超过10GB),写入长度为4的字符串。
所以提供了对LFS的支持。
要获取LFS功能,有两种办法:
1.在编译时加入 -D_FILE_OFFSET_BITS=64
2.在代码开头加入 #define _FILE_OFFSET_BITS 64

要使用过渡型的LFS API,必须在编译程序时定义 LARGEFILE64_SOURCE 功能测试宏。该 API 所属函数具有处理 64 位文件大小和文件偏移量的能力。命名为 fopen64(), open64(), lseek64(), truncate64(), stat64(), mmap64(), setrlimit64()。除了函数,好有数据类型:struct stat64, off64_t 。

注意,一旦使用LFS,off_t 输出时转换为 (long long) 型。
书中给了向printf()调用传递off_t值
off_t offset;
/*对offset赋值操作*/
printf("offset = %lld\n",(long long)offset);

/dev/fd目录

对于每个进程,内核提供一个特殊的虚拟目录/dev/fd,该目录包含/dev/fd/n形式的文件名,打开该目录下一个文件等同于复制相应的文件描述符。
/dev/fd实际上是一个符号链接,链接到linux所专有的/proc/self/fd目录.
在程序中,我们有时候需要创建一些临时文件,仅供其在运行期间使用。

#include <stdlib.h>
int mkstemp(char *template);

template 为路径名,其中最后6个字符必须为XXXXXX,这六个字符由系统自由分配,保证了文件名的唯一性,并通过template参数返回。文件用完后使用unlink系统调用返回。如下:

#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
    int fd;
    char template[] = "/tmp/aaaXXXXXX";  /*必须为字符数组,不可为常量*/
    fd = mkstemp(template);   /*创建*/
    printf("chuangjian chengong\n");
    unlink(template);  /*删除*/
    close(fd);
    return 0;
}

同样作用的还有函数tmpfile():

#include <stdio.h>
FILE *tmpfile(void);

tmpfile() 会创建一个名称唯一的临时文件,将返回一个文件流供 stdio 库函数使用。