實驗環(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)支持在不同進程中共享打開文件。
內(nèi)核為打開的文件維護的數(shù)據(jù)結(jié)構(gòu)
- 每個進程維護一張文件描述符表,表示這個進程打開的文件。與每個文件關(guān)聯(lián)的是
文件描述標志(close_on_exec,后續(xù)講解)
[圖片上傳失敗...(image-e7d824-1531646493558)]指向該文件表項的指針
- 內(nèi)核為所有打開的文件分別維持一張文件表,每個文件表項包含:
- 文件狀態(tài)標志(讀、寫、追加、同步和非阻塞等,后續(xù)講解)
- 當前文件的偏移量
- 指向該文件v節(jié)點表項的指針
- 每個進程打開一個文件都有一個v節(jié)點結(jié)構(gòu),v節(jié)點包含了文件類型和對此文件進行各種操作函數(shù)的指針。v節(jié)點還包含了該文件的i節(jié)點。這些信息是在打開文件時從從磁盤上讀入內(nèi)存的,所以,文件的所有信息都是隨時可用的。比如:i節(jié)點包含了文件的所有者、文件長度、指向文件實際數(shù)據(jù)塊在磁盤上所在位置的指針等(第四章詳細來研究典型的unix系統(tǒng)文件系統(tǒng)以及i節(jié)點)
linux沒有使用v節(jié)點,使用的是通用的i節(jié)點結(jié)構(gòu)。雖然兩者實現(xiàn)不同,但在概念上v節(jié)點和i節(jié)點是一樣的,都指向了文件系統(tǒng)系統(tǒng)特有的i節(jié)點結(jié)構(gòu)。上圖中就是一個例子。
- 多進程打開同一個文件,如下圖,兩個進程打開同一個文件,兩個進程有各自文件描述符表,分別指向各自的文件表項,兩個文件表項的v節(jié)點指針會指向同一個v節(jié)點表項
[圖片上傳失敗...(image-7d0415-1531646493559)] - 常見操作說明
- 完成每個write(無設(shè)置):更新文件表項中的當前文件偏移量,如果偏移量會比當前文件長度大,則將i節(jié)點表項中的當前文件長度設(shè)置為當前文件偏移量
- O_APPEND:設(shè)置文件表項中的文件狀態(tài)標志,每次進行write操作時,文件表項中的當前文件偏移量首先被設(shè)置為i節(jié)點表項中的文件長度。這就使得每次寫入都是追加
- lseek操作:只是修改文件表項中的當前偏移量,而不進行I/O操作
可能有多個文件描述符指向同一個文件表項,討論dup函數(shù)的時候,我們再來討論。此外fork之后也會發(fā)生同樣的情況,此時父進程和紫禁城各自的每一個文件描述符共享對應(yīng)的同一個文件表項(第八章來討論)。注意:文件描述符標志和文件狀態(tài)標志在作用范圍方面是有區(qū)別的,前者只用于一個進程的一個文件描述符,而后者則應(yīng)用于指向該給定文件表項的任何進程中的所有描述符。后面討論fcntl函數(shù)的時候再來討論如何獲取文件描述符標志和文件狀態(tài)標志。
原子操作
原子操作指的是由多步組成的一個操作。防止多個操作之間被別的操作打斷而導(dǎo)致數(shù)據(jù)不同步,從而導(dǎo)致多個操作的結(jié)果沒有達到預(yù)期
多進程讀寫以及pread和pwrite函數(shù)
single unix specification包括了xsi擴展,該擴展允許原子性地定位并執(zhí)行I/O
- 調(diào)用pread相當于調(diào)用lseek后調(diào)用read,但是有區(qū)別:
- 調(diào)用pread時,無法中斷其定位和讀操作
- 不更新當前文件偏移量
- 調(diào)用pwrite相當于調(diào)用lseek后調(diào)用write,但是也有類似的區(qū)別
- 測試代碼如下:
#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)]
功能:這兩個函數(shù)用于復(fù)制一個現(xiàn)有的文件描述符
- dup:返回的新文件描述符一定是當前可用文件描述符中的最小值
- dup2:可以指定新描述符的值,注意:
- 如果fd不是有效的文件描述符,那么調(diào)用失敗,并且fd2沒有關(guān)閉
- 如果fd是一個有效的文件描述符,然后fd2和fd的值一樣,那就啥事也不做,直接返回fd2的值
- 在成功地從其中一個系統(tǒng)調(diào)用返回之后,舊的和新的文件描述符可以互換使用。它們引用相同的打開文件描述(參見open(2)),從而共享文件偏移量和文件狀態(tài)標志;例如,如果使用lseek(2)在描述符上使用lseek(2)來修改文件偏移量,那么另一個描述符的偏移量也會發(fā)生變化。
但是注意: 這兩個描述符不共享文件描述符標志(close-on-exec標志)。close-on-exec標志(FD_CLOEXEC;對于重復(fù)描述符,請參閱fcntl(2)。
- dup3:這個函數(shù)與dup2的類似,不同點在于fd2的文件描述符標志會可以被強制設(shè)置O_CLOEXEC;再者如果fd2等于fd,會返回失敗,errno=EINVAL
O_CLOEXEC模式和FD_CLOEXEC選項
- 調(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選項,效果和使用O_CLOEXEC選項open函數(shù)相同,但分別調(diào)用open、fcnt兩個函數(shù),不是原子操作,多線程環(huán)境中存在競態(tài)條件,故用open函數(shù)O_CLOEXEC選項代替之。
- 調(diào)用open函數(shù)O_CLOEXEC模式打開的文件描述符,或是使用fcntl設(shè)置FD_CLOEXEC選項,這二者得到(處理)的描述符在通過fork調(diào)用產(chǎn)生的子進程中均不被關(guān)閉。
- 調(diào)用dup族類函數(shù)得到的新文件描述符將清除O_CLOEXEC模式。
O_CLOEXEC模式和FD_CLOEXEC選項的代碼測試(包括fork、單進程、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單進程設(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
此時,sleep進程沒有占用O_CLOEXEC.tmp文件
/* 1.2單進程無設(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
此時,sleep進程占用O_CLOEXEC.tmp文件,說明sleep進程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 2.1單進程設(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
此時,sleep進程沒有占用O_CLOEXEC.tmp文件
/* 2.2單進程無設(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
此時,sleep進程占用O_CLOEXEC.tmp文件,說明sleep進程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 3.1多進程設(shè)置O_CLOEXEC選項 */
[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會讓子進程繼承父進程的文件描述符標志(寫實復(fù)制)
/* 4.1dup對O_CLOEXEC選項的影響 */
[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清除了新文件描述符的文件描述符標志O_CLOEXEC
sync、fsync和fdatasync函數(shù)
[圖片上傳失敗...(image-c358c8-1531646493559)]
傳統(tǒng)unix系統(tǒng)實現(xiàn),在內(nèi)核中設(shè)有緩沖區(qū)告訴緩存或頁告訴緩存,大多數(shù)磁盤I/O都通過緩沖區(qū)進行。當我們向文件寫入數(shù)據(jù)時,內(nèi)核現(xiàn)將數(shù)據(jù)復(fù)制到緩沖區(qū)中,然后排入隊列,晚點再寫入磁盤,這種方式叫做延遲寫。
如果這些緩沖區(qū)又需要存放別的數(shù)據(jù)時,內(nèi)核會把這些緩沖區(qū)的數(shù)據(jù)寫入磁盤。為了讓文件系統(tǒng)中的數(shù)據(jù)和緩沖區(qū)中的數(shù)據(jù)保持一致。提供了上述的函數(shù)
-
函數(shù)詳解:
- sync只是將所有緩沖區(qū)中的數(shù)據(jù)排入寫入隊列,然后就返回,并不代表實際寫磁盤操作結(jié)束。一般有個update的系統(tǒng)守護進程周期性調(diào)用sync函數(shù),注意:sync命令也只是調(diào)用這個函數(shù)
- fsync:這個對指定的文件描述符起作用,并且等到寫磁盤操作結(jié)束才結(jié)束,并且更新文件的屬性,fsync可用于數(shù)據(jù)庫這樣的應(yīng)用程序,需要及時寫
- fdatasync:類似于fsync,但是只是影響文件的數(shù)據(jù)部分,不影響文件的屬性
fcntl函數(shù)
[圖片上傳失敗...(image-ca8955-1531646493559)]
功能作用
- 復(fù)制一個已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
- 獲取、設(shè)置文件描述符標志(cmd=F_GETFD或F_SETFD)
- 獲取、設(shè)置文件狀態(tài)標志(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文件描述符標志
- F_DUPFD_CLOEXEC:復(fù)制文件描述符,并且可以設(shè)置FD_CLOEXEC, 測試如下:
#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:獲取對應(yīng)fd的文件描述符標志(目前就FD_CLOEXEC一個標志)
- F_SETFD:為對應(yīng)的fd設(shè)置文件描述符標志
- F_SETFL:獲取文件狀態(tài)標志
[圖片上傳失敗...(image-6fb50d-1531646493559)]
注意:O_RDONLY、O_WRONLY、O_RDWR、O_EXEC、O_SEARCH這幾個值并不是占一個bit位(ps: 在本機上沒找O_EXEC、O_SEARCH這兩個宏),可以用O_ACCCMODE獲得某個文件的文件狀態(tài)標志
- F_SETFL:設(shè)置文件狀態(tài)標志
- F_GETOWN:獲取當前接受SIGIO和SIGURG(TCP帶外數(shù)據(jù)、緊急模式,也可以用select來處理)信號的進程id或進程組(第14張再來討論異步I/O信號)
- F_SETOWN:設(shè)置用于接受SIGIO和SIGURG信號的進程id或者進程組id,arg為負數(shù)時,則表示arg絕對值的一個進程組id
總結(jié)簡單測試
- 代碼如下:
#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,同步寫
當給文件描述符設(shè)置這個標志后,每次write都要等待,直至數(shù)據(jù)已寫到磁盤上再返回。在UNIX系統(tǒng)中,通常write只是將數(shù)據(jù)排入隊列,實際寫磁盤可能在之后。使用這個O_SYNC后,這樣一來,當write返回時就知道數(shù)據(jù)已確實寫到了磁盤上,一面在系統(tǒng)異常時產(chǎn)生數(shù)據(jù)丟失
程序運行時,設(shè)置O_SYNC標志會增加系統(tǒng)時間和時鐘時間。下圖為apue中從一個磁盤文件中將492.6MB的數(shù)據(jù)復(fù)制到另外一個文件。然后對比設(shè)置了O_SYNC標志的程序。
[圖片上傳失敗...(image-2ca378-1531646493559)]在macOS X上的實驗結(jié)果如下:
[圖片上傳失敗...(image-541483-1531646493559)]我的實驗環(huá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)測試結(jié)果:
| 操作 | 用戶CPU(s) | 系統(tǒng)CPU(s)| 時鐘時間(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操作命令,但是每個設(shè)備驅(qū)動程序可以定義他自己專屬的ioctl命令
[圖片上傳失敗...(image-15fe1b-1531646493559)]磁帶操作可以進行文件結(jié)束操作、倒帶、越過指定的記錄,用本章的函數(shù)是難以表示這些操作的,但可以用ioctl來操作
第18章來討論使用ioctl獲取、設(shè)置終端窗口的大小,19章來討論ioctl訪問偽終端的功能
/dev/fd
- 打開/dev/fd/n等效于復(fù)制文件描述符n,如下
fd = open("/dev/fd/0", mode);
等價于
fd = dup(0);
0和fd共享一份文件表項
- 如果描述符n之前被打開為只讀,那么我們也只能對fd進行讀操作,而且下列操作是成功的,但是還是不能進行寫操作
fd = open("/dev/fd/0", O_RDWR);
- 也可以對/dev/fd路徑進行creat操作
在linux上這么做必須很小心,linux的實現(xiàn)是使用了符號鏈接,對其進行creat可能會導(dǎo)致底層文件斷裂