久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

為什么Linux內核常常用Unsigned Long來代替指針

163次閱讀
沒有評論

共計 3458 個字符,預計需要花費 9 分鐘才能閱讀完成。

這篇文章給大家介紹為什么 Linux 內核常常用 Unsigned Long 來代替指針,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

昨天我犯了一個錯誤把指針和整數“混淆”的錯誤,幸得隊友王童鞋指正,今早起床,我把這個心得花一點時間記錄下來。

大抵掌握一個技術或者知識都是這三個階段:

不知道自己不知道;

知道自己不知道;

知道自己知道。

比較難突破的是“不知道自己不知道”的階段,因為“不知道自己不知道”,所以才往往特別自信,覺得“老子天下第一”。基本上,本文要記錄的一個小點,也是一個我從“不知道自己不知道”到“知道自己知道”的過程。

我們都知道(???),指針和整數在 C 語言里面是兩種不同含義的:

指針:主要是為了方便引用 (Dereferencing) 一個內存地址, Dereferencing is used to access or  manipulate data contained in memory location pointed to by a  pointer。所以指針的目的其實就是為了這樣的讀寫操作:

*p = a; b = *p;

整數:整數是一個數值,它的主要目的是為了加減等計算、比對、做數組下標、做索引之類的。它的目的不是為了引用一個內存。指針和整數 (這里主要是 unsigned  long,因為 unsigned long 的位數一般等于 CPU 可尋址的內存地址位數) 本身是八竿子打不著的,但是它們之間的一個有趣聯系是:

如果我們只是關心這個地址的值,而不是關心通過這個地址去訪問內存,這個時候,內核經常喜歡用 unsigned long 代替指針。

我們下面來看 2 個不同的場景:

指針是指針?

copy_from_user(void *to, const void __user *from, unsigned long n); copy_to_user(void __user *to, const void *from, unsigned long n);

在這 2 個函數里面,void __user *from,void __user  *to 都清楚地表明用戶空間的虛擬地址是一個指針。這 2 個函數這樣做的原因是非常清晰的,我就是要去 Dereference 用戶空間的地址,進行內存拷貝的,所以它的目的是為了通過指針來訪問內存。

類似的例子比如 file_operations 里面 read、write 什么的:

指針是整數?

/** * get_user_pages() - pin user pages in memory * @start: starting user address * ... */ long get_user_pages( unsigned long start, unsigned long nr_pages, unsigned int gup_flags, struct page **pages, struct vm_area_struct **vmas)

注釋清楚地寫明,start 是用戶態的起始地址:@start: starting user address

所以,本質上,和 copy_from_user()里面的 void __user *from 一樣的,但是這里它用的是 unsigned long start  !!! 不是 void __user * start !!!

原因非常清楚,get_user_pages()只關心 start 這個數值本身,它用于去運算、查找、比對。它不是要:

*start = 100;

類似的例子還有:

long pin_user_pages( unsigned long start, unsigned long nr_pages, unsigned int gup_flags, struct page **pages, struct vm_area_struct **vmas);

更加不要提著名的 find_vma():

/* Look up the first VMA which satisfies addr   vm_end, NULL if none. */ struct vm_area_struct *find_vma( struct mm_struct *mm, unsigned long addr);

它根據 addr 用戶態地址,在進程的 mm 里面去找到 addr 位于的 VMA。顯然,這個時候,它的目的是為了完成 addr 與進程每個 VMA 起始和結束地址的比多。

這個時候,我們來看看 VMA 結構體的長相,就更加有意思了。我們都知道,VMA 是為了記錄進程每一段虛擬地址空間的(比如代碼段、數據段、堆、棧、mmap 等):

然后我們看看 VMA 的定義:

看到沒有,vm_start 和 vm_end 都是妥妥的 unsigned long 啊!!!

我于是試圖弄清楚這么做的科學依據是什么,發現 LDD3 里面赫然寫著這么一段話(LDD3 第 11 章 289 頁):

它的科學依據是,既然你不是為了 dereferencing,我就讓你 dereferencing 不了,免得你又跑去 dereferencing,從而導致 bug。有的人說,我強制轉化 unsigned  long 為指針,不就可以訪問了嗎?

你不是還是需要強制轉換不是? 你強制轉換之前,會想一下,這個地方指針為啥是個整數呢? 你想明白了,說不定就不去訪問了。這樣它實際達到了震懾心靈的效果。

到這里,我們談的都還是虛擬地址,那么下面我們來談下物理地址。

物理地址是指針?

在一個有 MMU 的系統中,物理地址從來都不是指針。物理地址,從骨子里就是一個整數!!! 我記得之前經常有人往內核發 patch,把物理地址用個指針

* p 來描述,這是錯到根子里面的事情,所以每次都被罵地狗血淋頭。

因為你根本不可能用物理地址去 Dereferencing 什么東西。物理地址在內核的描述是:

它要么是一個 32 位的整數,要么是一個 64 位的整數。

那么,物理地址什么時候是一個指針呢? 在我還是一個小屁孩在大學玩《仙劍奇俠傳》和《軒轅劍:天之痕》的時候,我那個時候玩單片機,單片機里面沒有 MMU,所以也沒虛擬地址的概念,都是妥妥地通過物理地址“指針”來訪問內存的。

所以,如果一個人,一輩子都是玩單片機,它肯定會覺得我這篇文章在胡扯,因為他還是一個“不知道自己不知道”的階段。

模糊地帶

這里面仍然有一些模糊地帶,比如__get_free_page()、__get_free_pages()這樣的 API,返回的也是 unsigned  long 而不是指針:

往死里作也要把它弄成 unsigned long!!!

實際上,內核要有時候需要訪問__get_free_page()返回的內存,此前它需要進行強制類型轉換:

這看起來是不是特別地“精分”? 折騰來折騰去,折騰什么鬼呢? 這一篇文章有解釋:An (unsigned) long story about page  allocation

https://lwn.net/Articles/669015/

統計表明,90% 以上的情況下,__get_free_page()返回的 unsigned long 都會被強制轉化為指針!!! 但是這個返回 unsigned  long 是在歷史的第一天,Linux 的 0.01 就這樣了。Al Viro

https://lwn.net/Articles/668852/

但是改動實在太多了,改了接近 600 個文件,對此 Linus 的態度是:

No way in hell do we suddenly change the semantics of an interface that has  been around from basically day #1.

所以 Linus 的建議是,你真的需要一個指針的時候,你還是去調用 kmalloc()吧:

*kmalloc(size_t size, gfp_t flags);

絕世好代碼

很多工程師喜歡較真,就是必須在 0 和 1 之間做一個選擇。這個選擇有時候真的很難,所以 Linus 的意思是,0 和 1 踏馬地都不要,我不去跟你爭這個 0 和 1 的問題,我給你第三條路。這里,我看出了 Linus 的大智若愚啊!

我個人在工程里面對無意義的 0 和 1 的爭論也也沒什么好感,感覺在浪費我的時間。我對事情的看法是,爭一個 0.7 或者 0.3 就 OK 了。0.7 就是真方向,0.3 就是假方向。

關于為什么 Linux 內核常常用 Unsigned Long 來代替指針就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-25發表,共計3458字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 离岛区| 五寨县| 湟中县| 子长县| 苏尼特右旗| 高淳县| 渝中区| 巴楚县| 阿克苏市| 通榆县| 长子县| 正宁县| 金昌市| 清镇市| 隆回县| 虞城县| 巴里| 明溪县| 台中市| 麻栗坡县| 安徽省| 仁怀市| 黔西| 齐齐哈尔市| 建德市| 临沭县| 福贡县| 宝兴县| 永靖县| 兰坪| 城市| 江源县| 额济纳旗| 四平市| 大安市| 稻城县| 昌乐县| 饶河县| 西贡区| 阿坝县| 南宫市|