linux proc文件 write的原子性,unix/linux 探讨系统调用write的原子性(atomic)

当一个文件被多个进程或者多个线程同时操作时,会不会出现内容交错的现象。例如一个进程向文件写入“AAAA” ,使用语句(write( fd,  "AAAA",  4);),另一个进程向文件写入“BBBB”,语句为(write ( fd,  "BBBB",  4);)。那么最终文件的内容会不会出现“AABBBBAA” 的情况呢?这就涉及到write函数是否是原子操作的问题了。

如果write函数是原子操作,也即写入期间不允许进程或者线程的切换,那么就不会出现上面的情况,最终文件里的内容只可能为“AAAABBBB”, “BBBBAAAA”,“BBBB”, “AAAA”,这四种情况。你可能会感到奇怪,前面两种输出还好理解,后面是什么情况?这就与write函数的写入过程有关了,write分为定位和写入两个阶段,定位操作指定内容写入文件的位置(如 “起始”,“末尾”,或是中间某一位置 ,还记得lseek这个函数吧,它就是完成定位操作的)。试想这样一种情况,第一个进程定位完成后(pos=0),时间片结束,OS切换到第二个写入进程,该进程完成了所有操作后(pos=4)再还给第一个进程操作,由于第一个进程已经完成定位操作,现在开始写入,这样就会覆盖掉前面写的内容,导致出现上面的情况。那么如何解决这个问题呢?......嗯,还记得O_APPEND这个参数吗?它就是用来解决这个问题的,它使得定位与写入成为原子操作,也即每次写入的时候都定位到文件的末尾,然后完成写操作,中间不允许打断。这在打印log日志中可是非常好用哩!!!

如果write不是原子操作,那情况就非常复杂了,因为write可能会随时被打断,写入文件的内容就千奇百怪了。这样我们也就只能借助文件锁(多进程情形)或互斥锁(多线程情形),来解决文件写入问题了。当然文件锁和互斥锁都非常耗资源,而且效率较低,不推荐用这种锁机制。好在大多数的unix和linux都将write设计为原子操作,但这只限于文件,对于管道(pipe),套接字(socket),FIFO 又应当别论了。详情,请参考下面的这篇博文。http://os.51cto.com/art/201108/285324.htm

好了,一番没有例证的空谈都是无力的,下面我们就用代码测试write在多进程和多线程下是否是原子操作,以及在以上环境下,Log日志操作方法该如何设计。我的环境为:Linux CentOS 6.3  内核版本:2.6.32-279.el6.i686

#include

#include

#include

#include

#include

#defineFILE_NAME"demo.txt"

#defineCONSOLE"/dev/tty"

#defineerr_exit(m){perror(m); exit(1);}

intmain(){

intfd;

/************************参数说明如下:***********************************

*O_RDWR 对文件的操作权限,可读可写操作

*O_CREAT 如果文件不存在就新建该文件,用于记录写入内容;后面的0644设定文件的进入权限

*O_TRUNC 每次打开文件的时候都清空文件原先的内容

*O_APPEND 设定定位与写入操作的原子性,每次写入都追加到文件末尾

***********************************************************************/

fd = open(FILE_NAME, O_RDWR | O_CREAT | O_TRUNC | O_APPEND , 0644);

if(fd == -1) err_exit("open error");

intpid;

charbuf[4194304];//设定一个很大的数组4096*1024,测试其他进程是否可以打断该操作。

memset(buf, 'a', 4194303);//将该数组的内容设定为"aaaaaaa...\n",方便我们观测。

memset(buf+4194303,'\n',1);

if((pid = fork()) < 0) err_exit("fork error");

//利用fork产生子进程,共享同一个文件句柄,可以实现多进程的情形。

if(pid == 0){

int i = 0;

for(; i < 10; i++){

write(fd, buf, 4194304);

}

}else if(pid > 0){

int i = 0;

for(; i < 100; i++){

write(fd, "bbb\n", 4);

}

wait(-1);//等待子进程结束,防止僵尸进程的出现;

}

if(close(fd) == -1) err_exit("close error");

return 0;

}

然后我们用vi编辑器打开看一下内容,是否有“aaaaaaaaa......"中夹杂着“bbb”字符的情况,你会发现,这两个写入的过程是分开的,不会出现交叉的情况。当然如果你将buf的内容设定的非常大,超过了内核的缓存,则可能出现非原子操作的情况,当然这种情况我们应该避免发生。那么对于多线程的情况,该如何测试呢,请看下面的代码:

#include

#include

#include

#include

#include

#defineFILE_NAME"demo.txt"

#defineCONSOLE"/dev/tty"

#defineerr_exit(m){perror(m); exit(1);}

intfd;//设置成全局变量,方便下面的程序访问

intmain(){

/************************参数说明如下:***********************************

*O_RDWR 对文件的操作权限,可读可写操作

*O_CREAT 如果文件不存在就新建该文件,用于记录写入内容;后面的0644设定文件的进入权限

*O_TRUNC 每次打开文件的时候都清空文件原先的内容

*O_APPEND 设定定位与写入操作的原子性,每次写入都追加到文件末尾

***********************************************************************/

fd = open(FILE_NAME, O_RDWR | O_CREAT | O_TRUNC | O_APPEND , 0644);

pthread_t pth1, pth2, pth3, pth4;

charbuf[4194304];

void write_block(void *);

memset(buf, 'a', 4194302);//设定一个很大的数组4096*1024,测试其他进程是否可以打断该操作。

memset(buf+4194302,'\n', 1);//将该数组的内容设定为"aaaaaaa...\n",方便我们观测。

memset(buf+4194303,'\0', 1);//当然最后以0结尾,因为下面的函数用到strlen方法。

pthread_create(&pth1, NULL, write_block, buf);

pthread_create(&pth2, NULL, write_block, "bbbb\n");

pthread_create(&pth3, NULL, write_block, "cccc\n");

pthread_create(&pth4, NULL, write_block, "dddd\n");

pthread_join(pth1, NULL);

pthread_join(pth2, NULL);

pthread_join(pth3, NULL);

pthread_join(pth4, NULL);

return 0;

}

voidwrite_block(void* buf){

int i = 0;

intlen = strlen(buf);

for( ; i < 10; i++){

write(fd, buf,len);

}

}

同样的,我们用vi编辑器打开文件,发现并没有交错的情况出新,可以说明,write系统调用在buf大小不超过内核缓存的时候,他是原子操作,这样我们就证明了write的确时原子操作。上面的例子我们可以多做几遍,防止偶然事件的发生。

最后如果你想完成打印log日志的操作,可以将要打印的内容放入到一个buf中,最后一次调用write方法,这样就可以让输出的内容不会交错,便于查看内容。如果你想达到格式化输出的效果,可以使用sprintf函数,它与printf的用法是一样的,只是多个(char* buf )参数,最后将内容打印到buf中而不是屏幕上。我不建议分几次调用write方法,这样会怎加系统开销,因为系统会在内核和用户程序间来回切换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值