Linux系统中的竞争与冒险处理方法

嵌入式技术

1368人已加入

描述

    大家好,我是ST。

    今天主要和大家聊一聊,如何理解Linux系统中的竞争与冒险。

第一:竞争冒险基本简介

    假设有两个独立的进程A和进程B都对同一个文件进行追加写操作(也就是在文件末尾写入数据),每一个进程都调用了open函数打开了该文件,但未使用O_APPEND标志,此时,各数据结构之间的关系如图。每个进程都有它自己的进程控制块PCB,有自己的文件表(意味着有自己独立的读写位置偏移量),但是共享同一个inode节点(也就是对应同一个文件)。假定此时进程A处于运行状态,B未处于等待运行状态,进程A调用了lseek函数,它将进程A的该文件当前位置偏移量设置为1500字节处(假如这里是文件末尾),刚好此时进程A的时间片耗尽,然后内核切换到了进程B,进程 B 执行 lseek 函数,也将其对该文件的当前位置偏移量设置为 1500 个字节处(文件末尾)。然后进程 B 调用 write 函数,写入了 100 个字节数据,那么此时在进程 B 中,该文件的当前位置偏移量已经移动到了 1600 字节处。B 进程时间片耗尽,内核又切换到了进程 A,使进程 A 恢复运行,当进程 A 调用 write 函数时,是从进程 A 的该文件当前位置偏移量(1500 字节处)开始写入,此时文件 1500 字节处已经不再是文件末尾了,如果还从 1500字节处写入就会覆盖进程 B 刚才写入到该文件中的数据。

Linux

    以上给大家所描述的这样一种情形属于竞争状态(也成为了竞争冒险),操作共享资源的两个进程(或线程),其操作之后的所得到的结果往往是不可预期的,因为每个进程(或线程)去操作文件的顺序是不可预期的,即这些进程获得 CPU 使用权的先后顺序是不可预期的,完全由操作系统调配,这就是所谓的竞争状态。既然存在竞争状态,那么该如何规避或消除这种状态呢?接下来给大家介绍原子操作。

第二:原子操作方法
     所谓的原子操作,是有多步操作组成的一个操作,原子操作要么一步也不执行,一旦执行,必须要执行完所有步骤,不可能只执行所有步骤中的一个子集。

(1)O_APPEND实现原子操作

     前面提到过,进程A和进程B都对同一个文件进行追加操作,导致进程A写入的数据覆盖了进程B写入的数据,解决办法就是将“先定位到文件末尾”,然后写,这两个步骤组成一个原子操作。

     当open函数的 flags 参数中包含了 O_APPEND 标志,每次执行 write 写入操作时都会将文件当前写位置偏移量移动到文件末尾,然后再写入数据,这里“移动当前写位置偏移量到文件末尾、写入数据”这两个操作步骤就组成了一个原子操作,加入 O_APPEND 标志后,不管怎么写入数据都会是从文件末尾写,这样就不会导致出现“进程 A 写入的数据覆盖了进程 B 写入的数据”这种情况了。

(2)pread()和pwrite()

     pread()和pwrite()都是系统调用,与read()、write()函数的作用一样,用于读取和写入数据。区别在于,pread()和 pwrite()可用于实现原子操作,调用 pread 函数或 pwrite 函数可传入一个位置偏移量 offset 参数,用于指定文件当前读或写的位置偏移量,所以调用 pread 相当于调用 lseek 后再调用 read;同理,调用 pwrite相当于调用 lseek 后再调用 write。所以可知,使用 pread 或 pwrite 函数不需要使用 lseek 来调整当前位置偏移量,并会将“移动当前位置偏移量、读或写”这两步操作组成一个原子操作。

     pread、pwrite函数原型如下所示:

 

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

 

函数参数和返回值含义如下:

fd、buf、count 参数与 read 或 write 函数意义相同。

offset:表示当前需要进行读或写的位置偏移量。

返回值:返回值与 read、write 函数返回值意义一样。

虽然 pread(或 pwrite)函数相当于 lseek 与 pread(或 pwrite)函数的集合,但还是有下列区别:

(1)调用 pread 函数时,无法中断其定位和读操作(也就是原子操作);

 (2)不更新文件表中的当前位置偏移量。

实例代码实现:

 

#include 
#include 
#include 
#include 
#include 
#include 
int main(void)
{
 unsigned char buffer[100];
 int fd;
 int ret;
 /* 打开文件 test_file */
 fd = open("./test_file", O_RDWR);
 if (-1 == fd) {
 perror("open error");
 exit(-1);
 }
 /* 使用 pread 函数读取数据(从偏移文件头 1024 字节处开始读取) */
 ret = pread(fd, buffer, sizeof(buffer), 1024);
 if (-1 == ret) {
 perror("pread error");
 goto err;
 }
 /* 获取当前位置偏移量 */
 ret = lseek(fd, 0, SEEK_CUR);
 if (-1 == ret) {
 perror("lseek error");
 goto err;
 }
 printf("Current Offset: %d
", ret);
 ret = 0;
err:
 /* 关闭文件 */
 close(fd);
 exit(ret);
}

 

    在当前目录下存在一个文件test_file,直接使用pread函数读取100个字节数据,从偏移文件头部1024字节处,读取完成之后再使用lseek函数获取到文件当前位置偏移量,并将其打印出来。假如pread函数会改变文件中记录的当前位置偏移量,则打印出来的数据应用是1024+100=1124;如果不会改变文件表中的记录的当前位置偏移量,则打印出来的数据应该是0。

Linux

总结:利用Linux系统中的API函数进行操作的时候,竞争与冒险要进行规避与处理。

 审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分