linux 进程-线程文件操作注意点

为了测试globalmem在不带互斥保护下,多个地方进行IO操作,会引发竞态的问题。写了如下一个测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define MAX_THREAD_NUM 2
#define MAX_READ_BUFF_SIZE 1024
void* read_globalmem(void* args)
{
int fd = *((int*)args);
int ret;
char buf[MAX_READ_BUFF_SIZE];
printf("run a read thread\n");
while (1)
{
ret = read(fd, buf, MAX_READ_BUFF_SIZE - 1);
if (ret > 0)
{
buf[ret] = 0;
printf("read:%d bytes\n", ret);
printf("%s\n", buf);
}
}
return NULL;
}
void* write_globalmem(void* args)
{
int fd = *((int*)args);
int ret;
printf("run a write thread\n");
while (1)
{
ret = write(fd, "0123456789\n", 11);
if ( ret < 0 )
{
printf("write error\n");
}
else
{
printf("write %d bytes\n", ret);
}
}
return NULL;
}
int main(int argc, char** argv)
{
int fd, i, ret;
pthread_t tid[MAX_THREAD_NUM];
void *(* thread_fun) (void *);
char buf[MAX_READ_BUFF_SIZE];
fd = open("/dev/globalmem", O_RDWR);
if ( fd < 0 )
{
perror("open");
return -1;
}
for ( i = 0; i < MAX_THREAD_NUM; ++i )
{
if ( i % 2 )
{
thread_fun = read_globalmem;
}
else
{
thread_fun = write_globalmem;
}
ret = pthread_create(&tid[i], NULL, thread_fun, &fd);
if ( ret < 0 )
{
tid[i] = 0;
printf("create read thread failed\n");
}
}
for ( i = 0; i < MAX_THREAD_NUM; ++i )
{
if ( 0 != tid[i] )
{
pthread_join(tid[i], NULL);
}
}
close(fd);
return 0;
}

大概的操作就是开两个线程分别对文件/dev/globalmem进行读写操作。/dev/globalmem缓冲区4K大小。

而实际在globalmem驱动里面的打印是:

[114273.958892] read 1023 bytes(s) from 0
[114273.959057] read 1023 bytes(s) from 1023
[114273.959257] read 1023 bytes(s) from 2046
[114273.959404] read 1023 bytes(s) from 3069
[114273.959414] written 4 bytes(s) from 4092

可以看到write操作的offset是在read操作基础上进行的,很明显这是因为两个线程共用了一个文件表结构,实际情况如下图:

两个线程共用了一个文件表,导致文件偏移量相互的影响各种的访问。所以应该改为在读写线程中分别打开文件,这样整个进程拥有两个fd表。

还一种情况是如果这里改为两个父子进程呢?

fd = open("/dev/globalmem", O_RDWR);
pid = fork();
if ( pid == 0 )
{
while(1)
{
read();
}
}
else if ( pid > 0 )
{
while(1)
{
write();
}
}
close(fd);

会发现依然会出现文件偏移量相互干扰的情况,下图反应了fork调用后,父子进程与文件表,文件inode,vnode之间的关系(参考自APUE):

可以看到fork对父进程打开文件的拷贝只是拷贝了fd + 文件指针,实际的文件描述结构是同一份。所以,参考多线程的解决方法,要分别在各种的进程里面做打开文件的操作。

posted @   thammer  阅读(358)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· Qt个人项目总结 —— MySQL数据库查询与断言
点击右上角即可分享
微信分享提示