先分享一則有意思Q&A,來自The FreeBSD Funnies 17.4 。
Where does data written to* /dev/null* go?
It goes into a special data sink in the CPU where it is converted to heat which is vented through the heatsink / fan assembly. This is why CPU cooling is increasingly important; as people get used to faster processors, they become careless with their data and more and more of it ends up in /dev/null, overheating their CPUs. If you delete /dev/null (which effectively disables the CPU data sink) your CPU may run cooler but your system will quickly become constipated with all that excess data and start to behave erratically. If you have a fast network connection you can cool down your CPU by reading data out of /dev/random and sending it off somewhere; however you run the risk of overheating your network connection and / or angering your ISP, as most of the data will end up getting converted to heat by their equipment, but they generally have good cooling, so if you do not overdo it you should be OK.
寫入* /dev/null* 的資料去了哪裡?
它們進入了CPU的“垃圾槽”中并被轉化為熱量最終由散熱裝置吹走了 ;)這也是為什麼CPU的散熱越來越重要;随着人們習慣于使用那些飛快的CPU,他們也對那些被送入 /dev/null 的資料不那麼關心了——于是CPU可倒黴了。如果你删掉了/dev/null(這會馬上堵住CPU的“垃圾槽”。真的可以删掉。。。),你的CPU可能會舒服點但是你的系統也會因為那些無處可去的額外資料而馬上”便秘“。如果你的網挺快,也可以試試從dev/random讀取資料并發到網上(後面會講解random是如何工作的),以此來為你的CPU降溫。然而這也是有風險的,畢竟網絡和你的磁盤也會發熱(I/O橋??),電信營運商也會抓狂——那些資料會被他們的裝置轉化為熱量。不過他們的散熱一般很好,是以偶爾搞一搞也是無妨的 ; )
下面開始正經的說一說/dev下的Pseudo-devices:
/dev/random與/dev/urandom
首先介紹一下随機數的性質分類(學過密碼學的跳過吧)
- 随機性——不存在統計學的偏差,是完全雜亂的數列(前幾天看家偉做的一道線性同餘的題就是這種)。
- 不可預測性——不能從過去的數列推測出下一個出現的數。
- 不可重制性——除非将數列記錄下來,否則不可能重制相同的數列。
以上三個性質中,越往下越嚴格。密碼技術中所使用的随機數,僅僅具備随機性是不夠的,至少還要具備不可預測的性質。 一般的,将以上三個性質分别稱為”弱僞随機數“,”強僞随機數“,”真随機數“。
給真随機數舉一個簡單的例子:你不小心把一個玻璃杯打碎了,這個玻璃杯打碎過程中的破碎軌迹,破碎後碎片的分布對所有人來說都是随機的,也是目前幾乎無法完全重制的。真随機數生成麻煩,一般用來做僞随機數的種子,以此來利用擴張的随機性。
為了友善計算機獲得真随機數(不用你去砸杯子了),新型的X86 CPU中都内置了數字随機數生成器(Digital Random Number Generator, DRNG),并提供了RDSEED和RDRAND兩條指令。這種CPU生成随機數的原料(随機信号源)來自于電路中産生的熱噪聲。(如果你用過GnuPG生成對稱密鑰,它會提醒你移動滑鼠或者使用磁盤,這也是為了友善收集真随機數)。
從Linux 1.3.30開始,/dev有了random和urandom兩個character special files,通過這兩個接口,使用者可以使用核心的随機數字發生器。
随機數字發生器會收集硬體驅動産生的背景噪聲(真随機數),并把它們放在随機池裡。同時發生器會時時記錄下随機池裡剩餘的随機數。通過這個随機池,我們就可以産生很多僞随機數了。
那這兩個檔案有什麼差別呢?
當random檔案/裝置被讀時,它僅僅隻會傳回随機池裡的真随機數,是以random檔案适合于那些需要非常高安全性的場合——比如需要使用一次性密碼本或者作為其他僞随機數生成器的種子(比如密鑰生成器)。當随機池空的時候,random會暫停輸出知到新的熱噪聲被收集到池子中。(你可以試一下cat /dev/random,待到輸出停止後再移動一下滑鼠或者打開一個視訊,新的真随機數就會繼續被輸出。)
而當你讀urandom檔案/裝置時,它不會為了等待新的噪聲而暫停輸出。如果池子裡的真随機數不夠,它會調用核心裡的一個僞随機發生器來繼續産生随機數。是以,在這種情況下攻擊是可能的,但是到目前為止沒有發現針對這個檔案/裝置的有效攻擊。不放心的話就用random吧。
從3.16的核心開始,從urandom讀的話最多能收到32MB的随機數,從random的話有512bytes(2.6以前隻有340bytes)。
如果你向random或者urandom寫資料的話,寫入的資料會覆寫掉随機池裡的真随機數,但是這不會提高安全性;也不會提高從random讀出随機數的速度。
提醒一下,很多僞随機數的庫函數都僅僅滿足了随機性,例如C的rand,java的java.util.Random等等,以後寫安全方面的代碼時要注意。 (上次一航講的一個web500就用到了弱僞随機數的可預測性)
/dev/full
這個檔案通常被用來測試軟體是如何處理磁盤空間不夠的情況,當你向這個檔案寫資料時,會收到一個ENOSPC error。從該檔案讀的話會得到一連串NULL('\00')字元。
frank@under:~$ echo hello > /dev/full
bash: echo: write error: No space left on device
/dev/null
”talk is cheap, show me the code“,就按着kernel的源碼來講吧。
聲明:
static const struct file_operations null_fops = {
.llseek = null_lseek,
.read = read_null,
.write = write_null,
.read_iter = read_iter_null,
.write_iter = write_iter_null,
.splice_write = splice_write_null,
};
隻分析read_null和write_null,其他的類似。
read_null():
static ssize_t read_null(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
return 0;
}
可以看到,如果從null裡面讀取資料的話就是傳回有符号整型”0“,也不會處理使用者給他的file指針或者buffer指針和其他資料。,通常情況下這就是一個EOF——你得不到任何資料。
write_null():
static ssize_t write_null(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
return count;
}
這個也是”毫無用處“,僅僅隻是傳回我們告訴null我們想要寫入檔案的位元組大小(也是有符号整型)。通過這種通用的”寫入成功“傳回方式,使得一般的軟體将流定向到null可以收到”正确寫入“的消息,猜想這會友善軟體無用流的編寫。
/dev/zero
上面說到讀null會得到一個有符号整型“0”。那麼zero呢?你會得到一連串的NULL字元('\0')(說明處理了檔案指針)。
$ dd if=/dev/null of=file count=10
0+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000276193 s, 0.0 kB/s
$ dd if=/dev/zero of=file count=10
10+0 records in
10+0 records out
5120 bytes (5.1 kB) copied, 0.00090775 s, 5.6 MB/s
static const struct file_operations zero_fops = {
.llseek = zero_lseek,
.write = write_zero,
.read_iter = read_iter_zero,
.write_iter = write_iter_zero,
.mmap = mmap_zero,
.get_unmapped_area = get_unmapped_area_zero,
#ifndef CONFIG_MMU
.mmap_capabilities = zero_mmap_capabilities,
#endif
};
僅分析一下zero_lseek和write_zero。
zero_lseek:
#define zero_lseek null_lseek
/*
* Special lseek() function for /dev/null and /dev/zero. Most notably, you
* can fopen() both devices with "a" now. This was previously impossible.
* -- SRB.
*/
static loff_t null_lseek(struct file *file, loff_t offset, int orig)
{
return file->f_pos = 0;
}
f_pos定義在file結構體(定義在<linux/fs.h>),表示檔案目前的讀寫位置,是以現在可以appending的方式打開這兩個特殊檔案了。
write_zero:
#define write_zero write_null
static ssize_t write_null(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
return count;
}
這就很有意思了,看起來向zero寫檔案和null應該是一個效果。。。(不過這麼寫代碼的話别人會覺得奇怪吧)
看來null和zero很像兩兄弟,不過傳統上還是建議把null當成一個”隻寫“的黑洞,把zero當成一個隻讀的NULL字元(‘\0’)産生器吧。
ps:
- 大家可能會對null傳回的整型“0“和zero傳回的NULL字元 '\0'的差別感到疑惑,可以參考stackoverflow上一篇文章。
- /dev/zero 通常被用來建立swap。
- 詳細的文檔可以man full、urandom、zero等。
happy hacking!
參考:
- https://en.wikipedia.org/wiki//dev/random
- https://en.wikipedia.org/wiki/Null_device
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/char/mem.c?id=refs/tags/v4.9-rc3#n774
- https://stackoverflow.com/questions/3690273/did-i-understand-dev-urandom
- https://www.reddit.com/r/linuxquestions/comments/5b5wq5/eli5_how_does_devnull_work/
- https://unix.stackexchange.com/questions/63238/purpose-of-dev-zero
- https://unix.stackexchange.com/questions/254384/difference-between-dev-null-and-dev-zero
- 《圖解密碼學》第三版
- Linux Programmer's Manual