實(shí)驗(yàn)環(huán)境介紹
- gcc:4.8.5
- glibc:glibc-2.17-222.el7.x86_64
- os:Centos7.4
- kernel:3.10.0-693.21.1.el7.x86_64
文件共享
unix系統(tǒng)支持在不同進(jìn)程中共享打開文件。
內(nèi)核為打開的文件維護(hù)的數(shù)據(jù)結(jié)構(gòu)
- 每個(gè)進(jìn)程維護(hù)一張文件描述符表,表示這個(gè)進(jìn)程打開的文件。與每個(gè)文件關(guān)聯(lián)的是
文件描述標(biāo)志(close_on_exec,后續(xù)講解)
[圖片上傳失敗...(image-e7d824-1531646493558)]指向該文件表項(xiàng)的指針
- 內(nèi)核為所有打開的文件分別維持一張文件表,每個(gè)文件表項(xiàng)包含:
- 文件狀態(tài)標(biāo)志(讀、寫、追加、同步和非阻塞等,后續(xù)講解)
- 當(dāng)前文件的偏移量
- 指向該文件v節(jié)點(diǎn)表項(xiàng)的指針
- 每個(gè)進(jìn)程打開一個(gè)文件都有一個(gè)v節(jié)點(diǎn)結(jié)構(gòu),v節(jié)點(diǎn)包含了文件類型和對(duì)此文件進(jìn)行各種操作函數(shù)的指針。v節(jié)點(diǎn)還包含了該文件的i節(jié)點(diǎn)。這些信息是在打開文件時(shí)從從磁盤上讀入內(nèi)存的,所以,文件的所有信息都是隨時(shí)可用的。比如:i節(jié)點(diǎn)包含了文件的所有者、文件長(zhǎng)度、指向文件實(shí)際數(shù)據(jù)塊在磁盤上所在位置的指針等(第四章詳細(xì)來研究典型的unix系統(tǒng)文件系統(tǒng)以及i節(jié)點(diǎn))
linux沒有使用v節(jié)點(diǎn),使用的是通用的i節(jié)點(diǎn)結(jié)構(gòu)。雖然兩者實(shí)現(xiàn)不同,但在概念上v節(jié)點(diǎn)和i節(jié)點(diǎn)是一樣的,都指向了文件系統(tǒng)系統(tǒng)特有的i節(jié)點(diǎn)結(jié)構(gòu)。上圖中就是一個(gè)例子。
- 多進(jìn)程打開同一個(gè)文件,如下圖,兩個(gè)進(jìn)程打開同一個(gè)文件,兩個(gè)進(jìn)程有各自文件描述符表,分別指向各自的文件表項(xiàng),兩個(gè)文件表項(xiàng)的v節(jié)點(diǎn)指針會(huì)指向同一個(gè)v節(jié)點(diǎn)表項(xiàng)
[圖片上傳失敗...(image-7d0415-1531646493559)] - 常見操作說明
- 完成每個(gè)write(無設(shè)置):更新文件表項(xiàng)中的當(dāng)前文件偏移量,如果偏移量會(huì)比當(dāng)前文件長(zhǎng)度大,則將i節(jié)點(diǎn)表項(xiàng)中的當(dāng)前文件長(zhǎng)度設(shè)置為當(dāng)前文件偏移量
- O_APPEND:設(shè)置文件表項(xiàng)中的文件狀態(tài)標(biāo)志,每次進(jìn)行write操作時(shí),文件表項(xiàng)中的當(dāng)前文件偏移量首先被設(shè)置為i節(jié)點(diǎn)表項(xiàng)中的文件長(zhǎng)度。這就使得每次寫入都是追加
- lseek操作:只是修改文件表項(xiàng)中的當(dāng)前偏移量,而不進(jìn)行I/O操作
可能有多個(gè)文件描述符指向同一個(gè)文件表項(xiàng),討論dup函數(shù)的時(shí)候,我們?cè)賮碛懻摗4送鈌ork之后也會(huì)發(fā)生同樣的情況,此時(shí)父進(jìn)程和紫禁城各自的每一個(gè)文件描述符共享對(duì)應(yīng)的同一個(gè)文件表項(xiàng)(第八章來討論)。注意:文件描述符標(biāo)志和文件狀態(tài)標(biāo)志在作用范圍方面是有區(qū)別的,前者只用于一個(gè)進(jìn)程的一個(gè)文件描述符,而后者則應(yīng)用于指向該給定文件表項(xiàng)的任何進(jìn)程中的所有描述符。后面討論fcntl函數(shù)的時(shí)候再來討論如何獲取文件描述符標(biāo)志和文件狀態(tài)標(biāo)志。
原子操作
原子操作指的是由多步組成的一個(gè)操作。防止多個(gè)操作之間被別的操作打斷而導(dǎo)致數(shù)據(jù)不同步,從而導(dǎo)致多個(gè)操作的結(jié)果沒有達(dá)到預(yù)期
多進(jìn)程讀寫以及pread和pwrite函數(shù)
single unix specification包括了xsi擴(kuò)展,該擴(kuò)展允許原子性地定位并執(zhí)行I/O
- 調(diào)用pread相當(dāng)于調(diào)用lseek后調(diào)用read,但是有區(qū)別:
- 調(diào)用pread時(shí),無法中斷其定位和讀操作
- 不更新當(dāng)前文件偏移量
- 調(diào)用pwrite相當(dāng)于調(diào)用lseek后調(diào)用write,但是也有類似的區(qū)別
- 測(cè)試代碼如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define bool unsigned char
#define true 1
#define false 0
#ifdef TEST
#define READ(fd, buf_p, count, offset) pread(fd, buf_p, count, offset)
#else
#define READ(fd, buf_p, count, offset) read(fd, buf_p, count)
#endif
#define READ_TIME 10
#define FILE_PATH "/home/manjingliu/apue/part_3/tmp"
bool
create_thread(void *(*fun)(void *_arg), void *arg);
void *
pread_test(void *arg);
int
main(int argc, char *argv[])
{
system("touch /home/manjingliu/apue/part_3/tmp;echo 1234567890 > \
/home/manjingliu/apue/part_3/tmp");
int fd = open(FILE_PATH, O_RDONLY);
if (fd < 0) {
printf("opne %s error\n", FILE_PATH);
}
create_thread(pread_test, &fd);
create_thread(pread_test, &fd);
while (1)
sleep(1);
exit(EXIT_SUCCESS);
}
bool
create_thread(void *(*fun)(void *_arg), void *arg)
{
pthread_t tid;
int err = pthread_create(&tid, NULL, fun, arg);
if (err) {
printf("create thread error\n");
return false;
}
printf("create thread %ld successfully\n", tid);
if (pthread_detach(tid)) {
printf("detach thread %ld error(%s)\n", tid, strerror(errno));
return false;
}
printf("detach thread %ld successfully\n", tid);
return true;
}
void *
pread_test(void *fd)
{
printf("tid: %ld\n", pthread_self());
char buf[10] = {0};
int i = 0;
while (i < READ_TIME) {
if (READ(*((int *)fd), &buf[i], 1, i) <= 0)
printf("tid: %ld read error(%s)\n", pthread_self(), strerror(errno));
i++;
sleep(1);
}
i = 0;
while (i < READ_TIME)
printf("%c\n", buf[i++]);
printf("tid: %ld exit\n", pthread_self());
return ((void *)0);
}
read版本
result:
[manjingliu@localhost part_3]$ gcc -Wall 3_11.c -o 3_11 -std=c99 -lpthread
[manjingliu@localhost part_3]$
[manjingliu@localhost part_3]$ ./3_11
create thread 139985312143104 successfully
detach thread 139985312143104 successfully
create thread 139985303750400 successfully
detach thread 139985303750400 successfully
tid: 139985303750400
tid: 139985312143104
tid: 139985312143104 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985312143104 read error(Success)
tid: 139985312143104 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985312143104 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985312143104 read error(Success)
2
4
6
8
0
tid: 139985312143104 exit
1
3
5
7
9
tid: 139985303750400 exit
pread版本
result:
[manjingliu@localhost part_3]$ gcc -Wall 3_11.c -o 3_11 -std=c99 -lpthread -DTEST
3_11.c: In function ‘pread_test’:
3_11.c:79:3: warning: implicit declaration of function ‘pread’ [-Wimplicit-function-declaration]
if (READ(*((int *)fd), &buf[i], 1, i) <= 0)
^
[manjingliu@localhost part_3]$ ./3_11
create thread 139777443800832 successfully
detach thread 139777443800832 successfully
tid: 139777443800832
create thread 139777435408128 successfully
detach thread 139777435408128 successfully
tid: 139777435408128
1
2
3
4
5
6
7
8
9
0
tid: 139777443800832 exit
1
2
3
4
5
6
7
8
9
0
tid: 139777435408128 exit
dup和dup2函數(shù)
[圖片上傳失敗...(image-4fec68-1531646493559)]
功能:這兩個(gè)函數(shù)用于復(fù)制一個(gè)現(xiàn)有的文件描述符
- dup:返回的新文件描述符一定是當(dāng)前可用文件描述符中的最小值
- dup2:可以指定新描述符的值,注意:
- 如果fd不是有效的文件描述符,那么調(diào)用失敗,并且fd2沒有關(guān)閉
- 如果fd是一個(gè)有效的文件描述符,然后fd2和fd的值一樣,那就啥事也不做,直接返回fd2的值
- 在成功地從其中一個(gè)系統(tǒng)調(diào)用返回之后,舊的和新的文件描述符可以互換使用。它們引用相同的打開文件描述(參見open(2)),從而共享文件偏移量和文件狀態(tài)標(biāo)志;例如,如果使用lseek(2)在描述符上使用lseek(2)來修改文件偏移量,那么另一個(gè)描述符的偏移量也會(huì)發(fā)生變化。
但是注意: 這兩個(gè)描述符不共享文件描述符標(biāo)志(close-on-exec標(biāo)志)。close-on-exec標(biāo)志(FD_CLOEXEC;對(duì)于重復(fù)描述符,請(qǐng)參閱fcntl(2)。
- dup3:這個(gè)函數(shù)與dup2的類似,不同點(diǎn)在于fd2的文件描述符標(biāo)志會(huì)可以被強(qiáng)制設(shè)置O_CLOEXEC;再者如果fd2等于fd,會(huì)返回失敗,errno=EINVAL
O_CLOEXEC模式和FD_CLOEXEC選項(xiàng)
- 調(diào)用open函數(shù)O_CLOEXEC模式打開的文件描述符在執(zhí)行exec調(diào)用新程序中關(guān)閉,且為原子操作。
- 調(diào)用open函數(shù)不使用O_CLOEXEC模式打開的文件描述符,然后調(diào)用fcntl 函數(shù)設(shè)置FD_CLOEXEC選項(xiàng),效果和使用O_CLOEXEC選項(xiàng)open函數(shù)相同,但分別調(diào)用open、fcnt兩個(gè)函數(shù),不是原子操作,多線程環(huán)境中存在競(jìng)態(tài)條件,故用open函數(shù)O_CLOEXEC選項(xiàng)代替之。
- 調(diào)用open函數(shù)O_CLOEXEC模式打開的文件描述符,或是使用fcntl設(shè)置FD_CLOEXEC選項(xiàng),這二者得到(處理)的描述符在通過fork調(diào)用產(chǎn)生的子進(jìn)程中均不被關(guān)閉。
- 調(diào)用dup族類函數(shù)得到的新文件描述符將清除O_CLOEXEC模式。
O_CLOEXEC模式和FD_CLOEXEC選項(xiàng)的代碼測(cè)試(包括fork、單進(jìn)程、dup的情景)
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define FILENAME "O_CLOEXEC.tmp"
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(EXIT_FAILURE);\
} while (0)
int main()
{
int fd, fd2, val;
pid_t pid;
#ifdef _O_CLOEXEC
if ((fd = open(FILENAME, O_RDWR | O_CREAT | O_CLOEXEC, 0600)) < 0)
#else
if ((fd = open(FILENAME, O_RDWR | O_CREAT, 0600)) < 0)
#endif
err_sys("open error");
#ifdef _DUP
if ((fd2 = dup(fd)) < 0)
err_sys("dup error");
if (write(fd2, "123", 3) < 0)
err_sys("write error");
if ((val = fcntl(fd, F_GETFD)) < 0)
err_sys("fcntl(F_GETFD) error");
else
printf("O_CLOEXEC is %s set\n", (val & FD_CLOEXEC) ? "" : "not");
if ((val = fcntl(fd2, F_GETFD)) < 0)
err_sys("fcntl(F_GETFD) error");
else
printf("O_CLOEXEC is %s set in dup\n", (val & FD_CLOEXEC) ? "" : "not");
#endif
#ifdef _FCNTL_CLOEXEC
if ((val = fcntl(fd, F_GETFD)) < 0)
err_sys("fcntl(F_GETFD) error");
val |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, val) < 0)
err_sys("fcntl( F_SETFD) error");
#endif
#ifndef _FORK
if (execl("/usr/bin/sleep", "sleep", "10000", (void*)0) < 0)
err_sys("execl error");
#else
switch ((pid = fork())) {
case -1:
err_sys("fork error");
case 0:
sleep(10000);
break;
default:
sleep(10000);
break;
}
#endif
exit(EXIT_SUCCESS);
}
/* 1.1單進(jìn)程設(shè)置O_CLOEXEC,execl執(zhí)行命令 */
[manjingliu@localhost part_3]$ gcc -D_O_CLOEXEC -o cloexec 3_12.c
[manjingliu@localhost part_3]$ ./cloexec
[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+ 63509 59279 0 11:47 pts/3 00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63509
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 63509 manjingliu cwd DIR 253,0 117 1164545 /home/manjingliu/apue/part_3
sleep 63509 manjingliu rtd DIR 253,0 244 64 /
sleep 63509 manjingliu txt REG 253,0 33112 50381418 /usr/bin/sleep
sleep 63509 manjingliu mem REG 253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep 63509 manjingliu mem REG 253,0 2173512 42306 /usr/lib64/libc-2.17.so
sleep 63509 manjingliu mem REG 253,0 164240 32424 /usr/lib64/ld-2.17.so
sleep 63509 manjingliu 0u CHR 136,3 0t0 6 /dev/pts/3
sleep 63509 manjingliu 1u CHR 136,3 0t0 6 /dev/pts/3
sleep 63509 manjingliu 2u CHR 136,3 0t0 6 /dev/pts/3
此時(shí),sleep進(jìn)程沒有占用O_CLOEXEC.tmp文件
/* 1.2單進(jìn)程無設(shè)置O_CLOEXEC,execl執(zhí)行命令 */
[manjingliu@localhost part_3]$ gcc -o nocloexec 3_12.c
[manjingliu@localhost part_3]$ ./nocloexec
[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+ 63565 59279 0 11:55 pts/3 00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63565
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 63565 manjingliu cwd DIR 253,0 134 1164545 /home/manjingliu/apue/part_3
sleep 63565 manjingliu rtd DIR 253,0 244 64 /
sleep 63565 manjingliu txt REG 253,0 33112 50381418 /usr/bin/sleep
sleep 63565 manjingliu mem REG 253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep 63565 manjingliu mem REG 253,0 2173512 42306 /usr/lib64/libc-2.17.so
sleep 63565 manjingliu mem REG 253,0 164240 32424 /usr/lib64/ld-2.17.so
sleep 63565 manjingliu 0u CHR 136,3 0t0 6 /dev/pts/3
sleep 63565 manjingliu 1u CHR 136,3 0t0 6 /dev/pts/3
sleep 63565 manjingliu 2u CHR 136,3 0t0 6 /dev/pts/3
sleep 63565 manjingliu 3u REG 253,0 0 1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp
此時(shí),sleep進(jìn)程占用O_CLOEXEC.tmp文件,說明sleep進(jìn)程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 2.1單進(jìn)程設(shè)置FD_CLOEXEC,execl執(zhí)行命令 */
[manjingliu@localhost part_3]$
[manjingliu@localhost part_3]$ gcc -o fcntl_cloexec -D_FCNTL_CLOEXEC 3_12.c
[manjingliu@localhost part_3]$ ./fcntl_cloexec
[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+ 63622 59279 0 12:02 pts/3 00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63622
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 63622 manjingliu cwd DIR 253,0 155 1164545 /home/manjingliu/apue/part_3
sleep 63622 manjingliu rtd DIR 253,0 244 64 /
sleep 63622 manjingliu txt REG 253,0 33112 50381418 /usr/bin/sleep
sleep 63622 manjingliu mem REG 253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep 63622 manjingliu mem REG 253,0 2173512 42306 /usr/lib64/libc-2.17.so
sleep 63622 manjingliu mem REG 253,0 164240 32424 /usr/lib64/ld-2.17.so
sleep 63622 manjingliu 0u CHR 136,3 0t0 6 /dev/pts/3
sleep 63622 manjingliu 1u CHR 136,3 0t0 6 /dev/pts/3
sleep 63622 manjingliu 2u CHR 136,3 0t0 6 /dev/pts/3
此時(shí),sleep進(jìn)程沒有占用O_CLOEXEC.tmp文件
/* 2.2單進(jìn)程無設(shè)置FD_CLOEXEC,execl執(zhí)行命令 */
[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+ 63633 59279 2 12:05 pts/3 00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63633
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sleep 63633 manjingliu cwd DIR 253,0 178 1164545 /home/manjingliu/apue/part_3
sleep 63633 manjingliu rtd DIR 253,0 244 64 /
sleep 63633 manjingliu txt REG 253,0 33112 50381418 /usr/bin/sleep
sleep 63633 manjingliu mem REG 253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep 63633 manjingliu mem REG 253,0 2173512 42306 /usr/lib64/libc-2.17.so
sleep 63633 manjingliu mem REG 253,0 164240 32424 /usr/lib64/ld-2.17.so
sleep 63633 manjingliu 0u CHR 136,3 0t0 6 /dev/pts/3
sleep 63633 manjingliu 1u CHR 136,3 0t0 6 /dev/pts/3
sleep 63633 manjingliu 2u CHR 136,3 0t0 6 /dev/pts/3
sleep 63633 manjingliu 3u REG 253,0 0 1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp
此時(shí),sleep進(jìn)程占用O_CLOEXEC.tmp文件,說明sleep進(jìn)程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 3.1多進(jìn)程設(shè)置O_CLOEXEC選項(xiàng) */
[manjingliu@localhost part_3]$ gcc -o fork_cloexec -D_O_CLOEXEC -D_FORK 3_12.c
[manjingliu@localhost part_3]$ ./fork_cloexec
[manjingliu@localhost root]$ ps -ef | grep -v grep | grep fork
dbus 660 1 0 Jul10 ? 00:00:02 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
manjing+ 63646 59279 0 12:09 pts/3 00:00:00 ./fork_cloexec
manjing+ 63647 63646 0 12:09 pts/3 00:00:00 ./fork_cloexec
[manjingliu@localhost root]$ lsof -p 63646
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
fork_cloe 63646 manjingliu cwd DIR 253,0 198 1164545 /home/manjingliu/apue/part_3
fork_cloe 63646 manjingliu rtd DIR 253,0 244 64 /
fork_cloe 63646 manjingliu txt REG 253,0 8832 1164900 /home/manjingliu/apue/part_3/fork_cloexec
fork_cloe 63646 manjingliu mem REG 253,0 2173512 42306 /usr/lib64/libc-2.17.so
fork_cloe 63646 manjingliu mem REG 253,0 164240 32424 /usr/lib64/ld-2.17.so
fork_cloe 63646 manjingliu 0u CHR 136,3 0t0 6 /dev/pts/3
fork_cloe 63646 manjingliu 1u CHR 136,3 0t0 6 /dev/pts/3
fork_cloe 63646 manjingliu 2u CHR 136,3 0t0 6 /dev/pts/3
fork_cloe 63646 manjingliu 3u REG 253,0 0 1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp
[manjingliu@localhost root]$ lsof -p 63647
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
fork_cloe 63647 manjingliu cwd DIR 253,0 198 1164545 /home/manjingliu/apue/part_3
fork_cloe 63647 manjingliu rtd DIR 253,0 244 64 /
fork_cloe 63647 manjingliu txt REG 253,0 8832 1164900 /home/manjingliu/apue/part_3/fork_cloexec
fork_cloe 63647 manjingliu mem REG 253,0 2173512 42306 /usr/lib64/libc-2.17.so
fork_cloe 63647 manjingliu mem REG 253,0 164240 32424 /usr/lib64/ld-2.17.so
fork_cloe 63647 manjingliu 0u CHR 136,3 0t0 6 /dev/pts/3
fork_cloe 63647 manjingliu 1u CHR 136,3 0t0 6 /dev/pts/3
fork_cloe 63647 manjingliu 2u CHR 136,3 0t0 6 /dev/pts/3
fork_cloe 63647 manjingliu 3u REG 253,0 0 1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp
這說明fork會(huì)讓子進(jìn)程繼承父進(jìn)程的文件描述符標(biāo)志(寫實(shí)復(fù)制)
/* 4.1dup對(duì)O_CLOEXEC選項(xiàng)的影響 */
[manjingliu@localhost part_3]$ gcc -o dup_cloexec -D_O_CLOEXEC -D_DUP 3_12.c
[manjingliu@localhost part_3]$ ./dup_cloexec
O_CLOEXEC is set
O_CLOEXEC is not set in dup
說明dup清除了新文件描述符的文件描述符標(biāo)志O_CLOEXEC
sync、fsync和fdatasync函數(shù)
[圖片上傳失敗...(image-c358c8-1531646493559)]
傳統(tǒng)unix系統(tǒng)實(shí)現(xiàn),在內(nèi)核中設(shè)有緩沖區(qū)告訴緩存或頁(yè)告訴緩存,大多數(shù)磁盤I/O都通過緩沖區(qū)進(jìn)行。當(dāng)我們向文件寫入數(shù)據(jù)時(shí),內(nèi)核現(xiàn)將數(shù)據(jù)復(fù)制到緩沖區(qū)中,然后排入隊(duì)列,晚點(diǎn)再寫入磁盤,這種方式叫做延遲寫。
如果這些緩沖區(qū)又需要存放別的數(shù)據(jù)時(shí),內(nèi)核會(huì)把這些緩沖區(qū)的數(shù)據(jù)寫入磁盤。為了讓文件系統(tǒng)中的數(shù)據(jù)和緩沖區(qū)中的數(shù)據(jù)保持一致。提供了上述的函數(shù)
-
函數(shù)詳解:
- sync只是將所有緩沖區(qū)中的數(shù)據(jù)排入寫入隊(duì)列,然后就返回,并不代表實(shí)際寫磁盤操作結(jié)束。一般有個(gè)update的系統(tǒng)守護(hù)進(jìn)程周期性調(diào)用sync函數(shù),注意:sync命令也只是調(diào)用這個(gè)函數(shù)
- fsync:這個(gè)對(duì)指定的文件描述符起作用,并且等到寫磁盤操作結(jié)束才結(jié)束,并且更新文件的屬性,fsync可用于數(shù)據(jù)庫(kù)這樣的應(yīng)用程序,需要及時(shí)寫
- fdatasync:類似于fsync,但是只是影響文件的數(shù)據(jù)部分,不影響文件的屬性
fcntl函數(shù)
[圖片上傳失敗...(image-ca8955-1531646493559)]
功能作用
- 復(fù)制一個(gè)已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
- 獲取、設(shè)置文件描述符標(biāo)志(cmd=F_GETFD或F_SETFD)
- 獲取、設(shè)置文件狀態(tài)標(biāo)志(cmd=F_GETFL或F_SETFL)
- 獲取、設(shè)置異步I/O所有權(quán)(cmd=F_GETOWN或F_SETOWN)
- 獲取、設(shè)置記錄鎖(cmd=F_GETLK、F_SETT或F_SETLKW)
這次只先說明下除了記錄鎖相關(guān)的幾種
cmd操作
- F_DUPFD:復(fù)制文件描述符,清楚FD_CLOEXEC文件描述符標(biāo)志
- F_DUPFD_CLOEXEC:復(fù)制文件描述符,并且可以設(shè)置FD_CLOEXEC, 測(cè)試如下:
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define FILENAME "fcntl.tmp"
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(EXIT_FAILURE);\
} while (0)
int main()
{
int fd, fd2, fd3, val;
if ((fd = open(FILENAME, O_RDWR | O_CREAT | O_CLOEXEC, 0600)) < 0)
err_sys("open error");
if ((fd2 = fcntl(fd, F_DUPFD)) < 0)
err_sys("fcntl(F_GETFD) error");
if ((fd3 = fcntl(fd, F_DUPFD_CLOEXEC, 1)) < 0)
err_sys("fcntl(F_GETFD) error");
if ((val = fcntl(fd, F_GETFD)) < 0)
err_sys("fcntl(F_GETFD) error");
else
printf("O_CLOEXEC is %s set in fd\n", (val & FD_CLOEXEC) ? "" : "not");
if ((val = fcntl(fd2, F_GETFD)) < 0)
err_sys("fcntl(F_GETFD) error");
else
printf("O_CLOEXEC is %s set in F_DUPFD fd2\n", (val & FD_CLOEXEC) ? "" : "not");
if ((val = fcntl(fd3, F_GETFD)) < 0)
err_sys("fcntl(F_GETFD) error");
else
printf("O_CLOEXEC is %s set in F_DUPFD_CLOEXEC fd3\n", (val & FD_CLOEXEC) ? "" : "not");
exit(EXIT_SUCCESS);
}
result:
[manjingliu@localhost part_3]$ ./3_14
O_CLOEXEC is set in fd
O_CLOEXEC is not set in F_DUPFD fd2
O_CLOEXEC is set in F_DUPFD_CLOEXEC fd3
- F_GETFD:獲取對(duì)應(yīng)fd的文件描述符標(biāo)志(目前就FD_CLOEXEC一個(gè)標(biāo)志)
- F_SETFD:為對(duì)應(yīng)的fd設(shè)置文件描述符標(biāo)志
- F_SETFL:獲取文件狀態(tài)標(biāo)志
[圖片上傳失敗...(image-6fb50d-1531646493559)]
注意:O_RDONLY、O_WRONLY、O_RDWR、O_EXEC、O_SEARCH這幾個(gè)值并不是占一個(gè)bit位(ps: 在本機(jī)上沒找O_EXEC、O_SEARCH這兩個(gè)宏),可以用O_ACCCMODE獲得某個(gè)文件的文件狀態(tài)標(biāo)志
- F_SETFL:設(shè)置文件狀態(tài)標(biāo)志
- F_GETOWN:獲取當(dāng)前接受SIGIO和SIGURG(TCP帶外數(shù)據(jù)、緊急模式,也可以用select來處理)信號(hào)的進(jìn)程id或進(jìn)程組(第14張?jiān)賮碛懻摦惒絀/O信號(hào))
- F_SETOWN:設(shè)置用于接受SIGIO和SIGURG信號(hào)的進(jìn)程id或者進(jìn)程組id,arg為負(fù)數(shù)時(shí),則表示arg絕對(duì)值的一個(gè)進(jìn)程組id
總結(jié)簡(jiǎn)單測(cè)試
- 代碼如下:
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define FILENAME "fcntl.tmp"
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(EXIT_FAILURE);\
} while (0)
int main(int argc, char *argv[])
{
int val;
if (argc != 2)
err_sys("Usage: a.out <description#>");
if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
err_sys("fcntl error for fd %d", atoi(argv[1]));
switch (val & O_ACCMODE) {
case O_RDWR:
printf("read write");
break;
case O_RDONLY:
printf("read only");
break;
case O_WRONLY:
printf("write only");
break;
default:
printf("unknow access mode");
}
if (val & O_APPEND)
printf(", append");
if (val & O_NONBLOCK)
printf(", nonblocking");
if (val & O_SYNC)
printf(", synchronous");
#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
if (val & O_FSYNC)
printf(", synchronous writes");
#endif
putchar('\n');
exit(EXIT_SUCCESS);
}
reuslt:
[manjingliu@localhost part_3]$ ./3_15 O_CLOEXEC.tmp
read write, append
[manjingliu@localhost part_3]$ ./3_15 < /dev/tty
Usage: a.out <description#>
errno:0 Success
[manjingliu@localhost part_3]$ ./3_15 0 < /dev/tty
read only
[manjingliu@localhost part_3]$ ./3_15 1 > temp.foo
[manjingliu@localhost part_3]$ cat temp.foo
write only
[manjingliu@localhost part_3]$ ./3_15 2 2>> temp.foo
write only, append
[manjingliu@localhost part_3]$ ./3_15 5 5<> temp.foo
read write
O_SYNC,同步寫
當(dāng)給文件描述符設(shè)置這個(gè)標(biāo)志后,每次write都要等待,直至數(shù)據(jù)已寫到磁盤上再返回。在UNIX系統(tǒng)中,通常write只是將數(shù)據(jù)排入隊(duì)列,實(shí)際寫磁盤可能在之后。使用這個(gè)O_SYNC后,這樣一來,當(dāng)write返回時(shí)就知道數(shù)據(jù)已確實(shí)寫到了磁盤上,一面在系統(tǒng)異常時(shí)產(chǎn)生數(shù)據(jù)丟失
程序運(yùn)行時(shí),設(shè)置O_SYNC標(biāo)志會(huì)增加系統(tǒng)時(shí)間和時(shí)鐘時(shí)間。下圖為apue中從一個(gè)磁盤文件中將492.6MB的數(shù)據(jù)復(fù)制到另外一個(gè)文件。然后對(duì)比設(shè)置了O_SYNC標(biāo)志的程序。
[圖片上傳失敗...(image-2ca378-1531646493559)]在macOS X上的實(shí)驗(yàn)結(jié)果如下:
[圖片上傳失敗...(image-541483-1531646493559)]我的實(shí)驗(yàn)環(huán)境進(jìn)行簡(jiǎn)單實(shí)驗(yàn)(XFS文件系統(tǒng),32768bytes的緩沖區(qū)),代碼如下:
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/times.h>
#define BYTES (1 * 516581760L)
#define BUFF_LEN 32768
#define ORG_FILE "oo.org"
#define FILE_NAME1 "oo.1"
#define FILE_NAME2 "oo.2"
#define FILE_NAME3 "oo.3"
#define FILE_NAME4 "oo.4"
#define FILE_NAME5 "oo.5"
#define err_sys(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
exit(EXIT_FAILURE);\
} while (0)
#define err_dump(fmt, arg...) \
do { \
printf(fmt, ##arg);\
printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)
void
cal_func_time(void (*func)(void), int arg);
void
set_fl(int fd, int flags);
void
normal_write(void);
void
normal_o_sync_write(void);
void
normal_fdatasync_write(void);
void
normal_fsync_write(void);
void
normal_o_sync_fsync_write(void);
int
main(int argc, char *argv[])
{
cal_func_time(normal_write, 0);
cal_func_time(normal_o_sync_write, 0);
cal_func_time(normal_fdatasync_write, 0);
cal_func_time(normal_fsync_write, 0);
cal_func_time(normal_o_sync_fsync_write, 0);
exit(EXIT_SUCCESS);
}
void
cal_func_time(void (*func)(void), int arg)
{
int sc_clk_tck;
sc_clk_tck = sysconf(_SC_CLK_TCK);
struct tms begin_tms, end_tms;
clock_t begin, end;
begin = times(&begin_tms);
func();
end = times(&end_tms);
printf("real time: %lf\n", (end - begin) / (double)sc_clk_tck);
printf("user time: %lf\n",
(end_tms.tms_utime - begin_tms.tms_utime) / (double)sc_clk_tck);
printf("sys time: %lf\n",
(end_tms.tms_stime - begin_tms.tms_stime) / (double)sc_clk_tck);
}
void
set_fl(int fd, int flags)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0)
err_sys("fcntl F_GETFL error");
val |= flags;
if (fcntl(fd, F_SETFL, val) < 0)
err_sys("fcntl F_SETFL error");
}
void
normal_write(void)
{
ssize_t n;
char buf[BUFF_LEN];
printf("+++++++++++write disk normally+++++++++++++\n");
int open_fd = open(ORG_FILE, O_RDONLY);
if (open_fd < 0) {
err_sys("open %s error", ORG_FILE);
}
int open_fd1 = open(FILE_NAME1, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (open_fd1 < 0) {
err_sys("open open %s error", FILE_NAME1);
}
long has_read = 0;
while (1) {
n = read(open_fd, buf, BUFF_LEN);
if ( n < 0 ) {
err_sys("read error");
break;
} else if (n == 0) {
printf("has read over all: %ld\n", has_read);
break;
}
if (write(open_fd1, buf, n) != n) {
printf("write error\n");
break;
} else
has_read += n;
}
close(open_fd);
close(open_fd1);
}
void
normal_o_sync_write(void)
{
ssize_t n;
char buf[BUFF_LEN];
printf("+++++++++++normal_o_sync_write+++++++++++++\n");
int open_fd = open(ORG_FILE, O_RDONLY);
if (open_fd < 0) {
err_sys("open %s error", ORG_FILE);
}
int open_fd1 = open(FILE_NAME2, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (open_fd1 < 0) {
err_sys("open open %s error", FILE_NAME2);
}
set_fl(open_fd1, O_SYNC);
long has_read = 0;
while (1) {
n = read(open_fd, buf, BUFF_LEN);
if ( n < 0 ) {
err_sys("read error");
break;
} else if (n == 0) {
printf("has read over all: %ld\n", has_read);
break;
}
if (write(open_fd1, buf, n) != n) {
printf("write error\n");
break;
} else
has_read += n;
}
close(open_fd);
close(open_fd1);
}
void
normal_fdatasync_write(void)
{
ssize_t n;
char buf[BUFF_LEN];
printf("+++++++++++normal_fdatasync_write+++++++++++++\n");
int open_fd = open(ORG_FILE, O_RDONLY);
if (open_fd < 0) {
err_sys("open %s error", ORG_FILE);
}
int open_fd1 = open(FILE_NAME3, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (open_fd1 < 0) {
err_sys("open open %s error", FILE_NAME2);
}
long has_read = 0;
while (1) {
n = read(open_fd, buf, BUFF_LEN);
if ( n < 0 ) {
err_sys("read error");
break;
} else if (n == 0) {
printf("has read over all: %ld\n", has_read);
break;
}
if (write(open_fd1, buf, n) != n) {
printf("write error\n");
break;
} else {
if (fdatasync(open_fd1))
err_dump("fdatasync error");
has_read += n;
}
}
close(open_fd);
close(open_fd1);
}
void
normal_fsync_write(void)
{
ssize_t n;
char buf[BUFF_LEN];
printf("+++++++++++normal_fsync_write+++++++++++++\n");
int open_fd = open(ORG_FILE, O_RDONLY);
if (open_fd < 0) {
err_sys("open %s error", ORG_FILE);
}
int open_fd1 = open(FILE_NAME4, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (open_fd1 < 0) {
err_sys("open open %s error", FILE_NAME2);
}
long has_read = 0;
while (1) {
n = read(open_fd, buf, BUFF_LEN);
if ( n < 0 ) {
err_sys("read error");
break;
} else if (n == 0) {
printf("has read over all: %ld\n", has_read);
break;
}
if (write(open_fd1, buf, n) != n) {
printf("write error\n");
break;
} else {
if (fsync(open_fd1))
err_dump("fsync error");
has_read += n;
}
}
close(open_fd);
close(open_fd1);
}
void
normal_o_sync_fsync_write(void)
{
ssize_t n;
char buf[BUFF_LEN];
printf("+++++++++++normal_o_sync_fsync_write+++++++++++++\n");
int open_fd = open(ORG_FILE, O_RDONLY);
if (open_fd < 0) {
err_sys("open %s error", ORG_FILE);
}
int open_fd1 = open(FILE_NAME5, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (open_fd1 < 0) {
err_sys("open open %s error", FILE_NAME2);
}
set_fl(open_fd1, O_SYNC);
long has_read = 0;
while (1) {
n = read(open_fd, buf, BUFF_LEN);
if ( n < 0 ) {
err_sys("read error");
break;
} else if (n == 0) {
printf("has read over all: %ld\n", has_read);
break;
}
if (write(open_fd1, buf, n) != n) {
printf("write error\n");
break;
} else {
if (fsync(open_fd1))
err_dump("fsync error");
has_read += n;
}
}
close(open_fd);
close(open_fd1);
}
- Centos7.4上xfs文件系統(tǒng)測(cè)試結(jié)果:
| 操作 | 用戶CPU(s) | 系統(tǒng)CPU(s)| 時(shí)鐘時(shí)間(s) |
| :-------- | :--:| :--: |:--:|
| 正常寫到磁盤文件 | 0.000000 | 0.870000 | 8.940000 |
| 設(shè)置O_SYNC后寫到磁盤文件 | 0.020000 | 2.890000 | 16.170000 |
| 寫到磁盤后接著調(diào)用fdatasync | 0.110000 | 48.110000 | 117.990000 |
| 寫到磁盤后接著調(diào)用fsync | 0.080000 | 49.790000 | 116.660000 |
| 設(shè)置O_SYNC后寫到磁盤,接著調(diào)用fsync| 0.100000 | 51.290000 | 121.600000 |
ioctl函數(shù)
ioctl能夠?qū)崿F(xiàn)上述io函數(shù)都不能操作的其他io操作
終端I/O大量使用ioctl來操作,不過已經(jīng)有些函數(shù)開始替代ioctl的一些操作
系統(tǒng)為不同的設(shè)備定義了通用的ioctl操作命令,但是每個(gè)設(shè)備驅(qū)動(dòng)程序可以定義他自己專屬的ioctl命令
[圖片上傳失敗...(image-15fe1b-1531646493559)]磁帶操作可以進(jìn)行文件結(jié)束操作、倒帶、越過指定的記錄,用本章的函數(shù)是難以表示這些操作的,但可以用ioctl來操作
第18章來討論使用ioctl獲取、設(shè)置終端窗口的大小,19章來討論ioctl訪問偽終端的功能
/dev/fd
- 打開/dev/fd/n等效于復(fù)制文件描述符n,如下
fd = open("/dev/fd/0", mode);
等價(jià)于
fd = dup(0);
0和fd共享一份文件表項(xiàng)
- 如果描述符n之前被打開為只讀,那么我們也只能對(duì)fd進(jìn)行讀操作,而且下列操作是成功的,但是還是不能進(jìn)行寫操作
fd = open("/dev/fd/0", O_RDWR);
- 也可以對(duì)/dev/fd路徑進(jìn)行creat操作
在linux上這么做必須很小心,linux的實(shí)現(xiàn)是使用了符號(hào)鏈接,對(duì)其進(jìn)行creat可能會(huì)導(dǎo)致底層文件斷裂