Linux自定義文件描述符的操作指南
簡單回顧文件描述符和重定向
在linux
系統(tǒng)中,為每一個程序都定義了3
個文件描述符,分別是標(biāo)準(zhǔn)輸入:/dev/stdin
、標(biāo)準(zhǔn)輸出:/dev/stdout
和錯誤輸入:/dev/stderr
,如果用文件描述符id
表示,則從前到后分別是0
、1
、2
。
在linux
系統(tǒng)中,可以通過查詢/proc/進(jìn)程pid/fd
下的0
、1
、2
軟連接文件,可以查看文件描述符的具體指向。
# ls -l /proc/$$/fd total 0 lrwx------. 1 root root 64 Jun 4 22:54 0 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 4 22:54 1 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 4 22:54 2 -> /dev/pts/0 #
在程序初始運行中,系統(tǒng)會為分配這個3個文件描述符指向的設(shè)備或者文件,比如在上述例子中,都指向虛擬終端/dev/pts/0
,而重定向則是修改文件描述符的指向,比如將標(biāo)準(zhǔn)輸出指向到某個文件,而后續(xù)程序輸出則會寫到這個文件中,當(dāng)然也可以指向其他設(shè)備。
除此之外,還能額外創(chuàng)建其他的文件描述符。
自定義文件描述符的基本操作
在系統(tǒng)中,任何進(jìn)程都可以調(diào)用系統(tǒng)open
函數(shù),或者建立新的網(wǎng)絡(luò)連接來創(chuàng)建文件描述符,而這些文件描述符都屬于該進(jìn)程自己。而在linux
中,同樣也可以創(chuàng)建文件描述符,而該文件描述符依然屬于該進(jìn)程,可以看看這二者之間的差別。
進(jìn)程產(chǎn)生的文件描述符
關(guān)于進(jìn)程創(chuàng)建文件描述符,舉一個最簡單的例子,比如我們編寫如下的一段go
代碼:
package main import ( "fmt" "net" "os" "time" ) func main() { fmt.Println("pid: ", os.Getpid()) for i := 0; i < 10; i++ { _, err := os.Create(fmt.Sprintf("file_%d", i)) if err != nil { fmt.Println("open error", err) } } net.Dial("tcp", "192.168.1.1:22") net.Dial("tcp", "192.168.1.2:80") time.Sleep(time.Hour * 1) }
上述代碼,首先打印了進(jìn)程的pid
,而后在當(dāng)前目錄下新建了file_0
到file_9
這10個文件,創(chuàng)建了的文件都沒有進(jìn)行close
操作,也就是說,文件句柄還在進(jìn)程中,然后使用net
庫創(chuàng)建了2個tcp
連接,這是還是沒有close
操作,最后使用sleep
睡眠了1個小時。
測試一下進(jìn)程創(chuàng)建文件描述符號,首先進(jìn)行編譯:
go build -o fdtests
而后執(zhí)行該可執(zhí)行文件:
# ./fdtests pid: 1362
得到了pid
之后,新開一個終端,查看該進(jìn)程的fd
信息。
# ls -l /proc/1362/fd/ total 0 lrwx------. 1 root root 64 Jun 4 23:45 0 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 4 23:45 1 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 4 23:45 10 -> /root/file_5 lrwx------. 1 root root 64 Jun 4 23:45 11 -> /root/file_6 lrwx------. 1 root root 64 Jun 4 23:45 12 -> /root/file_7 lrwx------. 1 root root 64 Jun 4 23:45 13 -> /root/file_8 lrwx------. 1 root root 64 Jun 4 23:45 14 -> /root/file_9 lrwx------. 1 root root 64 Jun 4 23:45 15 -> socket:[22365] lrwx------. 1 root root 64 Jun 4 23:45 16 -> socket:[22366] lrwx------. 1 root root 64 Jun 4 23:45 2 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 4 23:45 3 -> /root/file_0 lrwx------. 1 root root 64 Jun 4 23:45 4 -> anon_inode:[eventpoll] lrwx------. 1 root root 64 Jun 4 23:45 5 -> anon_inode:[eventfd] lrwx------. 1 root root 64 Jun 4 23:45 6 -> /root/file_1 lrwx------. 1 root root 64 Jun 4 23:45 7 -> /root/file_2 lrwx------. 1 root root 64 Jun 4 23:45 8 -> /root/file_3 lrwx------. 1 root root 64 Jun 4 23:45 9 -> /root/file_4 #
通過上面的例子,可以看到眾多的文件句柄,其中0
、1
、2
分別是系統(tǒng)創(chuàng)建的默認(rèn)的句柄,還可以看到指向文件file_*
的句柄,以及2個socket
句柄,是最后的2個tcp
連接。其中有2個特殊的句柄anon_inode
,這個暫不討論。
上述這些句柄都是屬于進(jìn)程的,其他進(jìn)程是無法直接使用的,也需要調(diào)用系統(tǒng)open
函數(shù)來實現(xiàn)自己的句柄才行。
使用命令創(chuàng)建的文件描述符
在linux
中,會為每一個進(jìn)程都分配0
、1
、2
這三個文件描述符,包括使用的終端,而自定義的文件描述符必須從3
開始,而linux
建議使用3
到9
這幾個文件描述符,當(dāng)然,這只是建議,你可以不遵守,只要創(chuàng)建的文件描述符id
不超過系統(tǒng)允許最大的值即可,在linux
系統(tǒng)中使用如下命令查詢允許最大的文件描述符:
ulimit -n
當(dāng)然也可以動態(tài)修改它,比如將其修改為65535
,只需要在n
的后面添加即可:
ulimit -n 65535
創(chuàng)建文件描述符
在linux
中,創(chuàng)建一個可讀可寫的文件描述符,使用<>
關(guān)鍵字即可,例如:
exec 3<>/root/a.log
如上命令創(chuàng)建了一個可讀可寫的文件描述符3
,指向/root/a.log
,在linux
中創(chuàng)建的文件描述符是在/dev/fd
目錄中,例如:
# ls -l /dev/fd/ total 0 lrwx------. 1 root root 64 Jun 5 23:47 0 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 5 23:47 1 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 5 23:47 2 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 5 23:47 3 -> /root/a.log lr-x------. 1 root root 64 Jun 5 23:47 4 -> /proc/1852/fd #
注意,該目錄/dev/fd
是一個虛擬目錄,不同的進(jìn)程查看該目錄會得到不一樣的結(jié)果,即:該目錄下的句柄,只能創(chuàng)建該句柄的進(jìn)程所使用,其他進(jìn)程均無法使用。
向文件描述符寫入數(shù)據(jù)
使用重定向?qū)懭雰?nèi)容,比如上面文件描述符id
為3
,即:
echo "123" >& 3
注意,>&
是一個關(guān)鍵字,表示向文件描述符寫入數(shù)據(jù),而3
則表示文件描述符id
,注意:沒有>>&
這種寫法,這種寫法是錯誤的。
如果要將一個標(biāo)準(zhǔn)錯誤的數(shù)據(jù)寫入到對應(yīng)的文件描述符中,需要用到2>&
關(guān)鍵字,即:
abcd 2>& 3
都知道沒有abcd
這個命令,所以解釋權(quán)會向錯誤輸出報錯找不到命令,使用2>&
將該報錯寫入到文件描述符id
為3
中。
文件描述符讀取數(shù)據(jù)
使用<&
關(guān)鍵字可以讀取文件描述符的內(nèi)容,比如:
cat <& 3
可以讀取文件描述符id
為3
的內(nèi)容,但是實際執(zhí)行后,你會發(fā)現(xiàn),什么內(nèi)容都沒有,如:
# cat <& 3 #
這是因為linux
中有一個叫做文件指針的概念(offset
),在向該文件描述符寫入內(nèi)容的時候,已經(jīng)同步將指針移動了到了最后,所以在讀取的時候,什么內(nèi)容也沒有。需要重置offset
為開頭,即重新設(shè)置一下文件描述符:
# exec 3<>/root/a.log # cat <& 3 123 -bash: abcd: command not found #
如下即可讀取到文件描述符指向文件的內(nèi)容了。
關(guān)閉文件描述符
使用如下命令可以關(guān)閉id
為3
的文件描述符:
exec 3>&-
其中,3>&-
不能有任何空格,否則會報錯。
關(guān)閉后,/dev/fd/
文件中就沒有相關(guān)文件的文件描述符了。
# ls -l /dev/fd/ total 0 lrwx------. 1 root root 64 Jun 5 23:49 0 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 5 23:49 1 -> /dev/pts/0 lrwx------. 1 root root 64 Jun 5 23:49 2 -> /dev/pts/0 lr-x------. 1 root root 64 Jun 5 23:49 3 -> /proc/1293/fd #
創(chuàng)建只讀、只寫文件描述符
上述是創(chuàng)建了一個可讀寫的文件測試服,文件指針會隨著寫入變化而變化,linux
還允許創(chuàng)建只讀、只寫的文件描述符,比如:
創(chuàng)建只寫文件描述符id
為5
,指向文件/root/a1.log
,命令如下:
# exec 5> /root/a1.log
可以使用查看/dev/fd/5
權(quán)限,如下:
# ls -l /dev/fd/5 l-wx------. 1 root root 64 Jun 5 23:49 /dev/fd/5 -> /root/a1.log #
其中它的權(quán)限為-wx
,只有寫和執(zhí)行權(quán)限,沒有讀權(quán)限。
所以,若讀取的話,會報錯:
# cat <& 5 cat: -: Bad file descriptor #
創(chuàng)建只讀文件描述符id
為6
,同樣指向文件/root/a1.log
,命令如下:
# exec 6< /root/a1.log
同樣的,該文件權(quán)限只有讀和執(zhí)行權(quán)限,沒有寫權(quán)限。
# ls -l /dev/fd/6 lr-x------. 1 root root 64 Jun 5 23:47 /dev/fd/6 -> /root/a1.log #
這個時候使用文件描述符5
來寫數(shù)據(jù),使用文件描述符6
來去讀數(shù)據(jù)。
# echo '123' >& 5 # cat <& 6 123 # cat <& 6 #
讀取完成后,文件指針會移動到讀取后的位置,所以重復(fù)讀取是沒有用的。
文件描述符小例子
使用命名管道來限制多進(jìn)程同時執(zhí)行,代碼如下:
#!/bin/bash mkfifo pipe exec 3<>./pipe for i in $(seq 2) do echo "${i}" >&3 done for i in $(seq 10) do { read -u 3 id echo id: ${id} time: $(date +"%F %T") id: ${i} sleep 3 echo ${id} >&3 }& done exec 3>&- wait rm -f pipe echo "done"
上述腳本利用了管道來限制多進(jìn)程同時執(zhí)行,首先使用mkdifo
是用來創(chuàng)建管道,而后定義了一個文件描述符id
為3
來指向該管道文件,第一個for
循環(huán)表示允許同時最多幾個進(jìn)程運行,上述定義的是2
個,而后定義了10
個進(jìn)程來同時運行,使用read -u
來讀取文件描述符指向文件的內(nèi)容,并且寫入id
變量,當(dāng)read
讀取不到管道數(shù)據(jù)數(shù)據(jù)的時候,會阻塞當(dāng)前進(jìn)程,由于提前寫入了2
個數(shù)據(jù),所以,只允許2
個進(jìn)程同時運行,并且執(zhí)行完畢后,將id
重新寫入到管道中,以便實現(xiàn)循環(huán)讀取。最后關(guān)閉文件描述符,刪除管道文件。
所以上述腳本執(zhí)行結(jié)果如下:
[root@localhost bash]# bash fd_test.sh id: 1 time: 2025-06-05 23:47:46 id: 7 id: 2 time: 2025-06-05 23:47:46 id: 3 id: 1 time: 2025-06-05 23:47:49 id: 2 id: 2 time: 2025-06-05 23:47:49 id: 4 id: 1 time: 2025-06-05 23:47:52 id: 6 id: 2 time: 2025-06-05 23:47:52 id: 5 id: 1 time: 2025-06-05 23:47:55 id: 1 id: 2 time: 2025-06-05 23:47:55 id: 8 id: 1 time: 2025-06-05 23:47:58 id: 10 id: 2 time: 2025-06-05 23:47:58 id: 9 done [root@localhost bash]#
可以通過輸出發(fā)現(xiàn),同一時間只有2個任務(wù)在同時運行。
總結(jié)
在linux
中,每個進(jìn)程打開文件、建立網(wǎng)絡(luò)連接,其實都是在文件描述符中增加相應(yīng)的id
,若超過系統(tǒng)設(shè)置的值,則會報錯Too many open files
。
在linux
中,可以手動指定文件描述符,相關(guān)操作如下,比如創(chuàng)建id
為3
的文件描述符各項操作:
創(chuàng)建只讀的文件描述符:
exec 3<filename
創(chuàng)建只寫的文件描述符:
exec 3>filename
創(chuàng)建可讀寫的文件描述符:
exec 3<>filename
進(jìn)行文件描述符讀取操作:
<&3
進(jìn)行文件描述符寫入操作:
>&3
最后是關(guān)閉文件描述符:
3>&-
最后介紹了一個小例子,使用文件描述符配合管道來實現(xiàn)控制shell
腳本的同時運行。
以上就是Linux自定義文件描述符的操作指南的詳細(xì)內(nèi)容,更多關(guān)于Linux自定義文件描述符的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Linux下安裝Python3和django并配置mysql作為django默認(rèn)服務(wù)器方法
下面小編就為大家?guī)硪黄狶inux下安裝Python3和django并配置mysql作為django默認(rèn)服務(wù)器方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10安裝Apache提示丟失VCRUNTIME140.DLL怎么辦
本文通過自己的實際情況,給大家分享了在安裝Apache時提示丟失VCRUNTIME140.DLL的解決辦法,非常的實用,有需要的小伙伴可以參考下。2016-03-03詳解如何在 CentOS 7 中添加新磁盤而不用重啟系統(tǒng)
本篇文章主要介紹了詳解如何在 CentOS 7 中添加新磁盤而不用重啟系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03Ubuntu報“無法解析域名cn.archive.ubuntu.com“問題解決辦法
在Ubuntu系統(tǒng)上使用sudo?apt?update命令更新時可能遇到“無法解析域名cn.archive.ubuntu.com”的問題,這通常是因為cn.archive.ubuntu.com的鏡像資源不穩(wěn)定,為解決此問題,可以更換為穩(wěn)定性好、速度快的鏡像源,需要的朋友可以參考下2024-11-11