一、文件共享
  • 在介绍dup()函数之前,先介绍一些文件的共享

文件的打开

  • 每个进程打开文件时,一般有三项:
    • ①进程表项中的一个进程指向于一个文件表项
    • ②每个进程都有一个属于自己的文件标项
    • ③文件表项的节点指针指向于真实的文件
  • 下图是两个不同的进程分别打开不同的文件的演示:

APUE编程:14---文件I/O之(文件共享、文件原子操作(pread()、pwrite()))_文件共享

文件共享

  • 概念:我们知道一个文件可以被多个不同的进程同时打开
  • 例如:两个不同的进程打开同一个文件
  • 先用文件指针打开自己的文件表项
  • 然后通过文件表项的节点指针指向于文件真实内容

APUE编程:14---文件I/O之(文件共享、文件原子操作(pread()、pwrite()))_原子操作_02

二、原子操作

open()函数的O_APPEND参数介绍

  • 如果我们要将数据追加到一个文件尾端。早起的UNIX系统并不支持O_APPEND参数,会用以下的代码实现 (每次写入之前都使用lseek函数来指定文件指针的偏移量)
if (lseek(fd, 0L, 2) < 0) /*position to EOF*/
    perror("lseek error");
if (write(fd, buff, 100) != 100) /*and write*/
    perror("write error");
  • 上面代码的弊端:如果我们有两个进程同时对这个文件进行写入。A进程使用lseek对文件的1500字节进行写入,B进程也使用lseek对文件的1500字节进行写入。本来AB进程的目的都是想在文件的尾端进行写入,如果此时某个进程先执行,那么文件的内容增加,另外一个内容再去写入时就不会在尾端写入了,而是插入写入
  • 原子操作:上面代码弊端的逻辑操作是“”先定位到文件尾端,再写入“,使得两个函数分开调用,这样会产生错误。UXIX就提供了O_APPEND这个原子操作,在逻辑上综合了上面先lseek再write的操作,就是每次在写入时都追加写入,这样每次尾追写入就不需要调用fseek函数了

open()函数的O_CREAT和O_EXCL参数介绍

  • 如果我们判断一个文件是否存在,如果不存在就创建这个文件,可能会建立下面的代码
if ((fd = open(pathname, O_WRONLY)) <0)
{
    if (errno == ENOENT) 
    {
        if ((fd = creat(pathname, mode)) < 0)
            err_sys("creat error");
    } 
    else
        err_sys("open error");
}
  • 上面代码的弊端如果一个进程打开这个文件不存在正要去创建它时,突然另一个进程提前创建了它,那么就会出现问题,这段程序的creat就会将另一个进程写入的数据擦去
  • 原子操作:open()函数提供O_CREAT和O_EXCL这两个参数配合使用,如果去创建文件但文件已存在就会open()失败

pread()、pwrite()

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
//参数4:文件起始的偏移量
//返回值:成功返回读到的字节数;若达到文件尾,返回0;出错,返回-1

ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
//参数4:文件起始的偏移量
//返回值:成功返回写入的字节数;出错,返回-1
  • 这两个函数允许原子性地定位并执行I/O
  • 这两个函数免去了先调用fseek再read/write,将这两个步骤结合在一起
  • pread相当于调用lseek后调用read,特点如下:
    • 调用pread时,无法中断其定位和读操作
    • 不更新当前文件偏移量
  • pwrite相当于调用lseek后调用write,特点同上

原子操作概念

  • 原子操作指的是原本多部才能达到目的的操作结合为一个操作
  • 如果该操作原子地执行,要么执行完所有的步骤,要么一步也不执行,不可能只执行所有步骤中的一个子集