背景
最近碰到一個線上的bug鬓照,具體情況是進(jìn)程將24G左右的內(nèi)存buffer寫到磁盤上弃甥,但是每次write都寫不完24G惶室,然后重試導(dǎo)致磁盤寫滿斋枢,服務(wù)不可用帘靡。
在修這個bug的時候,對于到底write最大能寫多少字節(jié)的數(shù)據(jù)產(chǎn)生了濃厚的興趣瓤帚,寫了一個測試程序基本上每次最多寫到2G多一點(diǎn)描姚。
在研究了一段內(nèi)核代碼后一切霍然開朗。
write限制
write函數(shù)在以下三種情況下可能寫入的字節(jié)數(shù)小于指定的字節(jié)數(shù):
- 在底層的物理介質(zhì)上沒有足夠的空間
- RLIMIT_FSIZE的限制
- 寫入被信號打斷
從內(nèi)核代碼來看戈次,count在大于MAX_RW_COUNT的情況下轩勘,會賦值為MAX_RW_COUNT
而MAX_RW_COUNT是一個宏,展開為:INT_MAX & PAGE_MASK
INT_MAX也是一個宏怯邪,展開為((int)(~0U>>1))绊寻,也就是無符號數(shù)0取反后右移一位轉(zhuǎn)換成int類型,也就是2^31.
PAGE_MASK也是一個宏悬秉,展開為(~(PAGE_SIZE-1))榛斯,而PAGE_SIZE展開為(_AC(1,UL) << PAGE_SHIFT),PAGE_SHIFT的值為12搂捧,也就是每頁的大小是212,也就是說1左移12位懂缕,PAGE_SIZE的值為212允跑,然后PAGE_SIZE-1取反
最后MAX_RW_COUNT的值為INT_MAX & PAGE_MASK,也就是說MAX_RW_COUNT的值是int的最大值最后12位屏蔽掉搪柑,保持4K地址對齊
所以理論上講聋丝,每次write可寫的buff大小是231-212=2147479552,這也是與實(shí)際的測試結(jié)果相一致
測試代碼
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
printf("%d %s",sig,secret);
}
void sigtermHandler(int sig) {
printf("%d\n",sig);
}
void setupSignalHandlers(void) {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = sigtermHandler;
sigaction(SIGTERM, &act, NULL);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
act.sa_sigaction = sigsegvHandler;
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGILL, &act, NULL);
return;
}
int main(int argc,char** argv){
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers();
//extern int errno;
struct rlimit limit;
limit.rlim_cur = RLIM_INFINITY;
limit.rlim_max = RLIM_INFINITY;
if(setrlimit(RLIMIT_FSIZE,&limit)){
printf("set limit failed\n");
}
long long buff_size = atoll(argv[1]);
char *buff = malloc(buff_size*sizeof(char));
int fd = open("io.dat", O_CREAT|O_WRONLY|O_TRUNC, 0644);
int ret = 0;
while(buff_size>0){
ret = write(fd,buff,buff_size);
printf("written %d bytes\n",ret);
if(ret<0){
printf("write error: %s\n",strerror(errno));
return 0;
}
buff_size-=ret;
if(buff_size==0) break;
}
printf("write %lld bytes\n",buff_size);
return 0;
}