Linux中的原子写操作

5
在Linux中,当写入管道时,如果数据小于或等于内存页面大小(至少在64位RHEL上为4k),操作系统提供了保证整个写入操作要么成功要么失败的功能,但是在多个进程同时进行写入时不会发生数据损坏。这也适用于向常规文件写入。
我的问题是,这种原子性是否是Linux虚拟内存的一个特性?如果是,考虑两个进程之间的共享内存场景,其中一个进程在调度程序进行写入操作时被换出。虚拟内存子系统是否确保进程正在写入的内存页面也被锁定,以便第二个进程无法写入同一页面?
这种页面级别的原子性仅适用于进程之间,还是在同一进程的线程之间也适用?

3
通常情况下,发送方和接收方都被锁在系统调用中。在系统调用中,内核实际上可以进行任何操作。写操作开始时,将用户空间的缓冲区复制到系统缓冲区中(需要获取锁,并且当然还需要足够的缓冲空间。这可能需要几个操作,例如从其他结构或用户进程中窃取一些内存用于缓冲区)。只有在内核完成所有操作后,用户进程才会被唤醒。由于内核和用户进程生活在不同的地址空间中,虚拟内存并不是一个真正的问题。 - wildplasser
4
对于管道,你需要使用limits.h中的PIPE_BUF,而不是内存页面大小(由POSIX指定)。 - loreb
根据@wildplasser的解释,原子性似乎不仅适用于进程之间,而且也适用于同一进程中的线程。正确吗? - Jimm
是的。原则上,所有系统调用都是原子性的:它们要么成功,要么失败。对于管道I/O,区分的是读/写PIPE_BUFF大小以下是原子性的。这意味着,如果您尝试写入更大的大小,则由另一个进程写入的缓冲区可能会与您的(两个)写入交错。(这使系统一次只能获取一个缓冲区,而不是在保持锁定的同时尝试抓取所有缓冲区)它还避免了缓冲区窒息,因为(系统调用)其他进程也被允许获取缓冲区。 - wildplasser
@wildplasser写调用提供数据及其大小。那么为什么内核要一次获取PIPE_BUFF呢?如果大小为1Mb,它应该向VM请求1Mb。至少这样做,一些系统调用会成功。对于缓冲窒息场景,请考虑大量系统调用执行写操作,超过PIPE_BUFF。在这种情况下,所有调用首先获取PIPE_BUFF数量的内存,然后当它们再次尝试获取时,所有调用都失败了。以PIPE_BUFF的块形式请求内存也可能导致获得非连续的内存。 - Jimm
显示剩余2条评论
1个回答

2
  1. 不,如果两个进程正在使用共享内存,则它们之间没有隐式锁。您将需要自行安排此类锁(如果锁的所有者被交换出,则您的其他进程必须等待,直到所有者被交换并在完成保持锁定状态时释放锁定)。

  2. 我认为不存在任何隐式(或显式)规则表明页面与整个其他内存不同。特定规则适用于写入管道和文件,即如果所有数据都适合一个页面,则操作系统可以将其作为一个块进行写入-我认为您会发现操作系统每次都会为其正在写入的资源保持一次性锁定。如果数据大于一页,则在释放锁定时,另一个进程[或线程]可能已准备好运行,因此从第一个进程“窃取”锁定。小于一页,则在一个锁定运行中执行整个写入。

但要明确的是,通常情况下,并没有针对内存页面的读取(或写入)隐式锁。它仅适用于某些特定函数。通常,特定函数也会具有某种类型的锁,以防止其他进程在相同函数中运行[至少使用给定资源——例如文件描述符或类似物]-当然,完全可以使某些其他进程从另一个文件中同时读取,而您的进程从或写入到您的文件,但是对于某些块大小保持锁定的原子性YOUR文件,而不是针对您的“一次编写整个莎士比亚作品”的系统调用,因为这可能会阻止一些其他重要进程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接