乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      linux下多進(jìn)程寫入文件的原子性

       印度阿三17 2019-03-07

      一、文件寫入的原子性

      管道在整個(gè)unix系統(tǒng)中有重要的基礎(chǔ)設(shè)施意義,它使unix工具設(shè)計(jì)的“職能簡單”原則得以實(shí)現(xiàn)的基礎(chǔ),不同的工具使用管道協(xié)調(diào)完成自己的功能,并把一個(gè)功能做好。一個(gè)想法的提出通常具有明確的場景和簡潔的原理,后來需求的不斷發(fā)展導(dǎo)致問題看起來極為復(fù)雜,就像我們現(xiàn)在社會的進(jìn)化,可能原始社會中大家都是餓了吃,困了睡,兩者都找不到就去死的節(jié)奏。 shell通過管道,讓各個(gè)工具協(xié)調(diào)工作,基本的方法也是通過管道,看shell的語法文件中,單單是重定向著一個(gè)語法就占用了十幾條。匿名管道之后,又有了命名管道,當(dāng)命名管道創(chuàng)建之后,多個(gè)進(jìn)程寫入同一個(gè)管道的可能就會變大。在這種情況下,一些鉆牛角尖的人就想確定下多進(jìn)程寫入一個(gè)管道時(shí),讀出方讀到的數(shù)據(jù)和寫入方的寫入是否一致,更糟糕的是,不同的寫入者一次寫入的內(nèi)容是否會與其他寫入者一次性寫入的數(shù)據(jù)交錯(cuò)在一起? 二、glibc中對于fprintf函數(shù)的實(shí)現(xiàn) 1、glibc的FILE鎖 其實(shí)想想printf的實(shí)現(xiàn),也有些細(xì)節(jié)。比方說,用戶提供的格式化字符串沒有任何限制,例如,對于簡單的 printf("%s%s", str1, str2) 這樣的命令,用戶態(tài)提供了兩個(gè)任意起始地址的字符串,寫入時(shí)需要將兩這個(gè)字符串放在一個(gè)連續(xù)的內(nèi)存空間中寫入內(nèi)核,此時(shí)printf應(yīng)該如何連接這個(gè)字符串。最為直觀的辦法就是分配一個(gè)足以包含兩個(gè)字符串長度的連續(xù)空間,把它們連接在一起,然后傳遞給系統(tǒng)調(diào)用。 這里只是最為簡單的情況,單單是打印一個(gè)hello world,沒有問題,考慮到格式化的任意性,這種把所有的結(jié)果字符串整理完成之后再寫入,明顯是不切合實(shí)際的。這相當(dāng)于要求任意的字符串都要有兩份相同的備份,并且對于連續(xù)空間的要求也過于苛刻。 glibc對于格式化文件輸出的代碼位于glibc-2.6\stdio-common\vfprintf.c,這個(gè)文件中使用了較多的宏,看起來有些詭異,但是并不影響我們對于此處實(shí)現(xiàn)的理解。從該文件的實(shí)現(xiàn)可以看到 ??_IO_flockfile?(s); …… ? f = lead_str_end = __find_specwc ((const UCHAR_T *) format); …… ? /* Process whole format string. ?*/ ? do ? ? { …… /* Write the following constant string. ?*/ outstring?(specs[nspecs_done].end_of_fmt, ?? specs[nspecs_done].next_fmt ?? - specs[nspecs_done].end_of_fmt); ? } all_done: ? if (__builtin_expect (workstart != NULL, 0)) ? ? free (workstart); ? /* Unlock the stream. ?*/ ??_IO_funlockfile?(s); ? _IO_cleanup_region_end (0); glibc的做法就是化整為零,在處理一個(gè)格式化輸出的時(shí)候首先加鎖,這里的“鎖”位于FILE結(jié)構(gòu)中,通過相同的FILE結(jié)構(gòu)訪問到相同的鎖,共享相同的互斥。反過來說,就是FILE中使用的fd相同,只要它們封裝在不同的FILE結(jié)構(gòu)中,它們之間的寫入也并不互斥。加上鎖之后,glibc不再一次性把所有的字符格式化完成之后傳遞給write函數(shù)執(zhí)行,而是只處理自己“基本職責(zé)”功能,就是掃描一個(gè)格式化字符串中的描述并逐個(gè)處理。對于上面的printf("%s%s", str1, str2)例子,vfpintf函數(shù)根據(jù)%s來講str1的內(nèi)容通過outstring傳遞給更底層的FILE結(jié)構(gòu)緩沖管理層。這個(gè)緩沖的大小默認(rèn)情況下在文件打開時(shí)通過stat函數(shù)獲得文件所在的文件系統(tǒng)的block大小,F(xiàn)ILE緩沖一個(gè)block作為自己的緩沖區(qū)大小 tsecer@harry #touch hehe tsecer@harry #stat hehe ? File: `hehe' ? Size: 0 ? ? ? ? ? ? ? Blocks: 0 ? ? ? ? ?IO Block:?4096?? regular empty file 2、FILE結(jié)構(gòu)的緩存機(jī)制 執(zhí)行outstring之后,該輸出內(nèi)容被放到緩沖層進(jìn)行排隊(duì)及溢出處理,這部分代碼主要位于genops.c文件中。當(dāng)一個(gè)緩沖區(qū)慢之后,執(zhí)行沖刷。也預(yù)讀緩存數(shù)據(jù),減少不必要的讀取操作。 迄今為止,可以看到進(jìn)程內(nèi)調(diào)用fprintf可以保證不同線程調(diào)用同一個(gè)FILE結(jié)構(gòu)的printf具有原子性。但是由于這個(gè)鎖只是進(jìn)程范圍內(nèi)有效的一個(gè)粒度,所以不同進(jìn)程之間對于同一個(gè)底層文件的寫入不能保證原子性。甚至對于同一個(gè)文件,如果通過不同fopen調(diào)用返回的FILE結(jié)構(gòu)寫入,也不能保證寫入的互斥型。 整個(gè)緩存的管理代碼比較繁瑣,而且當(dāng)前沒有遇到需要理解這部分代碼才能解釋的問題,所以這部分先掠過。 三、常規(guī)文件(ext2)對write系統(tǒng)調(diào)用的實(shí)現(xiàn) sys_write-->>vfs_write--->>>do_sync_write-->>generic_file_aio_write ?mutex_lock(&inode->i_mutex);? ?ret = __generic_file_aio_write_nolock(iocb, iov, nr_segs, &iocb->ki_pos);? ?mutex_unlock(&inode->i_mutex); 從這個(gè)調(diào)用關(guān)系中可以看到,整個(gè)文件的寫入經(jīng)過了系統(tǒng)級的mutex鎖,所以這個(gè)寫入是原子性的。也就是說,對于一次write系統(tǒng)調(diào)用寫入的內(nèi)容不會與系統(tǒng)中任意一個(gè)系統(tǒng)調(diào)用寫入的內(nèi)容重疊。 四、pipe文件系統(tǒng)對write的實(shí)現(xiàn) 1、真正write的實(shí)現(xiàn) ?

      sys_write-->>vfs_write--->>>do_sync_write-->>generic_file_aio_write

      ret = 0; mutex_lock(&inode->i_mutex); pipe = inode->i_pipe; ? if (!pipe->readers) { send_sig(SIGPIPE, current, 0); ret = -EPIPE; goto out; } ? /* We try to merge small writes */ chars = total_len & (PAGE_SIZE-1); /* size of the last buffer */ if (pipe->nrbufs && chars != 0) { …… } ? for (;;) { int bufs; ? if (!pipe->readers) { send_sig(SIGPIPE, current, 0); if (!ret) ret = -EPIPE; break; } bufs = pipe->nrbufs; if (bufs < PIPE_BUFFERS) { …… if (do_wakeup) { wake_up_interruptible_sync(&pipe->wait); kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN); do_wakeup = 0; } pipe->waiting_writers ; pipe_wait(pipe); pipe->waiting_writers--; } out: mutex_unlock(&inode->i_mutex); 2、互斥鎖操作 和其它函數(shù)一樣,在函數(shù)的最開始也煞有介事的加上了一把互斥鎖,所以很容易讓人以為每次寫入的操作是一個(gè)原子性操作。但是在寫入的循環(huán)中調(diào)用了一個(gè)特殊的pipe_wait操作, void pipe_wait(struct pipe_inode_info *pipe) { DEFINE_WAIT(wait); ? /* ?* Pipes are system-local resources, so sleeping on them ?* is considered a noninteractive wait: ?*/ prepare_to_wait(&pipe->wait, &wait, TASK_INTERRUPTIBLE | TASK_NONINTERACTIVE); if (pipe->inode) mutex_unlock(&pipe->inode->i_mutex); schedule(); finish_wait(&pipe->wait, &wait); if (pipe->inode) mutex_lock(&pipe->inode->i_mutex); } 在寫入過程中,如果寫入緩沖區(qū)已滿,write調(diào)用通過pipe_wait釋放互斥鎖,相當(dāng)于“秦人失其鹿,天下共逐之”,這個(gè)鎖就這樣從指縫間溜走。在pipe_read系統(tǒng)調(diào)用中,也會獲得這把鎖,由于管道內(nèi)核中是通過共享的環(huán)形緩沖區(qū)實(shí)現(xiàn)的,所以這一點(diǎn)不足為奇。 3、如果保證PIPE_BUFF以內(nèi)寫操作的原子性 在pipe_write函數(shù)中,首先嘗試將對頁面取模剩余部分和上次寫操作進(jìn)行合并 /* We try to merge small writes */ chars = total_len & (PAGE_SIZE-1); /* size of the last buffer */ if (pipe->nrbufs && chars != 0)? if (ops->can_merge && offset chars <= PAGE_SIZE) 這里的合并操作檢測其實(shí)非常嚴(yán)格,只有當(dāng)本次小于頁面部分和上個(gè)頁面剩余空間的和小于一個(gè)頁面的時(shí)候才可以進(jìn)行寫操作,這個(gè)地方是保證小于頁面大小寫入原子性的一個(gè)保障。舉個(gè)例子,當(dāng)前最后一個(gè)緩沖頁面剩余2K,此時(shí)有3K數(shù)據(jù)寫入,由于兩者之和大于一個(gè)頁面,所以不會將3K拆分到該頁面中,而是直接分配一個(gè)新的頁面,從而保證小于頁面大小寫入的原子性。這也說明了內(nèi)核中雖然為一個(gè)pipe預(yù)留了16個(gè)頁面緩沖,但是并不一定能緩存16個(gè)頁面的內(nèi)容。 在pipe_read側(cè),這個(gè)地方也進(jìn)行了優(yōu)化 if (!buf->len) { buf->ops = NULL; ops->release(pipe, buf); curbuf = (curbuf 1) & (PIPE_BUFFERS-1); pipe->curbuf = curbuf; pipe->nrbufs = --bufs; do_wakeup?= 1; } 只有讀空了一個(gè)頁面之后才進(jìn)行wakeup操作,這只是一個(gè)優(yōu)化,并不是用來保證寫操作的原子性。 posix中相關(guān)規(guī)定 POSIX.1-2001 says?that write(2)s of less than PIPE_BUF bytes must be atomic: the output data is written to the pipe as a contiguous sequence. Writes of more than?PIPE_BUF?bytes may be nonatomic: the kernel may interleave the data with data written by other processes. POSIX.1-2001 requires PIPE_BUF to be at least 512 bytes. (On Linux, PIPE_BUF is 4096 bytes.) The precise semantics depend on whether the file descriptor is nonblocking (O_NONBLOCK), whether there are multiple writers to the pipe, and on n, the number of bytes to be written: 這里也注意內(nèi)核中的 #define PIPE_BUFFERS (16) 和 #define PIPE_BUF PAGE_SIZE 并不相同,PIPE_BUFFERS定義為16并不是表示內(nèi)核保證16頁面寫入的原子性,這個(gè)只是為了減少讀寫操作的阻塞。 五、writev系統(tǒng)調(diào)用的原子性 sys_writev--->>>vfs_writev--->>do_readv_writev { fn = (io_fn_t)file->f_op->write; fnv = file->f_op->aio_write; } if (fnv) ret = do_sync_readv_writev(file, iov, nr_segs, tot_len, pos, fnv); else ret = do_loop_readv_writev(file, iov, nr_segs, pos, fn); 如果一個(gè)文件系統(tǒng)沒有提供aio_write操作,則執(zhí)行do_loop_readv_writev,這個(gè)操作循環(huán)調(diào)用文件的write接口并且循環(huán)中沒有加解鎖操作,明顯地,這個(gè)地方可能存在非原子性的地方。但是對于我們常見的ext2文件系統(tǒng),這點(diǎn)不用擔(dān)心,因?yàn)樗鼈兌继峁┝薬io_write操作。 關(guān)于這一點(diǎn),Google搜索驗(yàn)證下可以看到有人也有這種疑慮,但是沒有確切回復(fù),搜索函數(shù)名do_loop_readv_writev,第一個(gè)結(jié)果就是關(guān)于這個(gè)函數(shù)寫入原子性的質(zhì)疑。來源:http://www./content-3-132801.html

        本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多