嵌入式技术
大家好,我是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 刚才写入到该文件中的数据。
以上给大家所描述的这样一种情形属于竞争状态(也成为了竞争冒险),操作共享资源的两个进程(或线程),其操作之后的所得到的结果往往是不可预期的,因为每个进程(或线程)去操作文件的顺序是不可预期的,即这些进程获得 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函数原型如下所示:
#includessize_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系统中的API函数进行操作的时候,竞争与冒险要进行规避与处理。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !