我有一个非常具体的应用程序,我需要一个带持久存储的自动增量变量。
确切地说,我将int
变量的十进制表示存储在文件中。要从文件中生成下一个数字read()
,请将内容转换回int
,将1和write()
添加回文件。我不需要并发访问这些数据。一个进程中只有一个线程调用函数来检索自动增量编号。该程序在嵌入式环境中运行,没有人可以访问控制台,因此不应该担心安全问题。如果重要,它将在MIPS上的Linux 2.6.24上运行。
问题是,我没有获得100%可重复的结果。有时我会重复数字,这对我的申请来说是不可接受的。
我的实现如下。
在启动应用程序时,我有:
int fd = open("myfile", O_RDWR|O_CREAT|O_SYNC, S_IRWXU|S_IRWXG|S_IRWXO);
自动增量功能:
int get_current(int fd)
{
char value[SIZE];
lseek(fd, 0, SEEK_SET);
read(fd, value, SIZE);
return atoi(value);
}
int get_next(int fd)
{
char value[SIZE];
int cur = get_current(fd);
memset(value, 0, SIZE);
sprintf(value, "%d", cur + 1);
lseek(fd, 0, SEEK_SET);
write(fd, value, SIZE);
//fsync(fd); /* Could inserting this be the solution? */
return (cur + 1);
}
为了代码可读性,我故意遗漏了上面的错误检查。我有代码来检查所有系统调用的返回值。
代码最初由另一个人编写,现在我已经检测到了这个问题,解决它的第一步是找出可能导致它的原因。我担心它可能与缓存文件访问的方式有关。我知道当我write()
我没有保证数据实际到达物理媒介时,但是在没有调用read()
的情况下调用fsync()
并且仍能获得可预测的结果是否安全?如果是,那我就没有想法;)
感谢您的阅读。
答案 0 :(得分:5)
是的,写完后立即阅读是安全的。在类Unix系统中,当write()
返回时,数据安全地存在于内核缓冲池中,并将返回到需要读取数据的其他进程。使用O_SYNC,O_DSYNC,O_FSYNC(确保将数据写入磁盘)和Windows系统时,类似的注释也适用。显然,当aio_write()
调用返回时,异步写入将不会完成,但在完成信号通知时它将完成。
但是,出现问题是因为您没有确保一次只有一个进程或线程访问该文件。您必须确保您获得串行访问权限,这样您就不会同时从文件中读取两个进程(或线程)。这是DBMS术语中的“丢失更新”问题。
您需要确保一次只能访问一个进程。如果您的流程合作,您可以使用咨询锁定(通过POSIX系统上的fcntl()
)。如果您的流程不合作,或者您不确定,则可能需要强制锁定,或者完全使用其他技术。
答案 1 :(得分:0)
是的,如果你write()
到一个文件,然后read()
,你应该看到你刚才写的数据。例外情况是另一个进程或线程在此期间覆盖了文件,或者write()实际上是否失败。
答案 2 :(得分:0)
文件内容是实现原子计数器的一种非常糟糕的方法。你的数量有多大?如果它不是很大,一个简单的方法是写一个字节(无关紧要)来递增计数器,并使用fstat
(st_size
)来读取计数器。 ftrunc
可以将计数器重置为零。
实现所需内容的更简洁方法是对文件进行内存映射(使用mmap
)并且不仅存储计数,还存储初始化为进程共享的pthread_mutex_t
,以及更新计数时锁定它。
另一种可以使用mmap
的方法是,如果你有C1x原子类型(_Atomic int
),但你必须等待5到10年。 :-)或者您可以使用gcc intrinsics或asm进行原子操作。此解决方案具有迄今为止最佳的性能(略微优于pthread_mutex_t
方法,并且比write
方法快数百倍。)