1、原子(atomicity)操作的概念
执行流程所要完成的各个动作是不可中断的操作就叫原子操作。所有系统调用都是以原子操作方式执行的,内核保证了某系统调用中所有步骤作为独立操作而一次性执行完毕,中间不会被其它线程、进程所中断。

2、竞争状态
竞争状态是指操作共享资源的两个进程或线程,其结果取决于一个无法预期的顺序,即这两个进程或线程获得CPU使用权的先后顺序。

两个例子说明原子操作的重要性:
3、向文件尾部追加数据的原子性
1)多个进程/线程向同一文件尾部添加数据的场景十分普遍,比如系统软件向全局日志文件记录日志。为实现在尾部追加数据的目的,操作文件的两个进程会这么做:

if (lseek(fd, 0, SEEK_END) < 0) {
    perror("lseek");
    exit(1);
}

if (write(fd, buf, len) != len) {
    perror("write");
    exit(1);
}

这段代码存在的缺陷是:若进程一执行到lseek()和write()之间,被执行相同代码的进程二所中断,那么这两个进程会在写入数据之前将文件偏移到相同位置,而当进程一再次获得CPU使用权时,会覆盖第二个进程已写入的数据。因为执行的结果依赖于内核对两个进程的调度顺序,这就是竞争状态。
2)我们在从内核层的角度看待write()系统调用。write()的执行简化后:

L1:获取文件偏移指针的位置get_pos()
L2:执行写操作write_data()
L3:更新文件偏移指针的位置update_pos()

write()函数本身并非一个原子操作,真正的原子操作仅仅是write_data()操作,也就是说只有在执行write_data()过程中不会被打断,在get_pos()和write_data()或者write_data()和updata_data()之间是可能会被打断的。那么可能出现这样的竞争状态:
两个进程a向同一文件写入数据,假设进程a要写入10个‘a’,进程b要写入10个‘b’。进程a的write()操作执行到get_pos()时就失去CPU的使用权,此时它记录文件指针后进入等待执行状态,进程b得到CPU使用权,从get_pos()、write_data()到update_pos()一路执行,然后退出;此时进程a再次得到CPU使用权,即继续执行未执行的write_data()和updata_pos()操作。显然,这样一来,线程b写完成的数据‘b’就会被‘a’完全覆盖了。
要避免上述的竞争状态,毫无疑问,应用层中的lseek()改变文件指针偏移量、内核层中的获取/更新文件指针偏移量的操作都和数据写入操作纳入同一原子操作中,在打开文件时加入O_APPEND标志可以保证这一点。

4、独占方式创建一个文件
open()系统调用,同时指定O_EXCL和O_CREAT标志时,若要打开的文件已经存在,则open()将返回错误。这种当前进程就是文件的创建者。显然O_EXCL和O_CREAT标志是属于原子操作,这样文件是否已经存在的判断和不存在则创建文件的操作才不会被打断。

Logo

更多推荐