5. 文件I/O:深入探讨

5.1 原子性和竞争条件

原子性是一个在system call里经常能碰到的一个概念,所有的system call都以原子的方式执行。原子性可以类比在微观物理的概念上,很早以前人们大概认为原子不可以再分,所以原子化的程序也是一个不可被打断,需要作为一个整体执行的程序。对于每一个system call的所有步骤都可以被视为一个整体而不可被别的进程或者线程中断。

原子性之所以重要,是因为它可以帮助我们避免很多竞争条件race conditions。之所以会有竞争条件,是因为有的时候共享同一资源的两个进程或者线程进入CPU调用的顺序不明确从而导致可能的混乱。

在下面的内容当中,本书将讨论两种包含文件I/O的竞争条件,然后再进一步讨论如何使用open()消除并保证相关文件操作的原子性。

创造互斥文件

在之前的文章中 4. 文件I/O:通用I/O模型_猴子头头123的博客-CSDN博客提到说关于O_EXCL的大概使用:

O_EXCL  这个一般和O_CREAT一起使用。当文件已经存在的时候,则文件不可以被打开,并且要返回一个错误(errno EEXIST)。换句话说,O_EXCL|O_CREAT是用来确保该process只在没有该文件的情况下创建文件。这一个过程是以atomically的方式执行(原子操作:线程或进程执行过程中不会被打断,也就意味着不会有别的线程进程在同一时间段内使用同一个resource)。5.1中还会继续展开解释。
 

至于说为什么这个O_EXCL非常重要,我们可以看一个下面的例子,在例子当中本书展示了在缺失O_EXCL的情况下会发生什么:

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

正常来讲,如果只要一个进程,那么它会先打开上述 Open 1 位置的文件,结果发现不存在,所以之后会进入下面 open的内容部分当中,并新建一个新的文件。

fd = open(argv[1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);

 我们假设上述的进程为A。 当进程A执行完Open 1的任务之后,因为多进程调度的原因,这时候执行同样内容的进程B开始被执行(也许高优先级什么的),那么它会顺序执行Open1,然后在进入新建文件的过程,并且新建成功-->结束。这时候交还CPU给进程A,并且进程A也执行上述单行代码,不过因为只有O_WRONLY|O_CREAT的存在,该打开也会被成功执行,虽然并未实际生成新的文件。

那么这个时候,其实进程A会误认为自己才是那个生成新文件的进程,会输出一个进程A 生成文件xxx的内容。当然这并不是我们所期待的东西,所以这样的情况被我们称之为race condition,它会使得程序不再可靠。可以想象更奇特的例子比如打印机,如果进程A刚进入打印程序,进程B插入并占用打印机打印了内容B,这时候再回到进程A,但是进程A误以为自己已经打印了内容A,结果事实上我们只能得到一张上面写有内容B的A4纸,这大概是没有人想要得到的结果。

 下面这部分代码是为了帮助实现上面竞争条件而额外加入的内容。也就是当我们在命令行给入超过两个参数的时候它就会实现5秒的休眠,这个时候可以赶快去执行另一个进程则可以实现上面的race condition。

if (argc > 2) {             /* Delay between check and create */
   sleep(5);               /* Suspend execution for 5 seconds */
   printf("[PID %ld] Done sleeping\n", (long) getpid());
}

测试结果如下:

pi@raspberrypi:~/sysprog/learn_tlpi/build $ ./out tfile sleep &
[1] 4330
pi@raspberrypi:~/sysprog/learn_tlpi/build $ [PID 4330] File "tfile" doesn't exist yet
./out tfile 
[PID 4331] File "tfile" doesn't exist yet
[PID 4331] Created file "tfile" exclusively
pi@raspberrypi:~/sysprog/learn_tlpi/build $ [PID 4330] Done sleeping
[PID 4330] Created file "tfile" exclusively

[1]+  Done                    ./out tfile sleep

明显可知,这里的PID号4330即是上述A进程,PID号4331即是B进程。很明显B进程输出

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值