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 + 文件指针,实际的文件描述结构是同一份。所以,参考多线程的解决方法,要分别在各种的进程里面做打开文件的操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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数据库查询与断言