linux內核中的IS_ERR

2020-12-11 電子產品世界

在看內核源碼的時候,經常會遇到IS_ERR,比如在linux/arch/arm/kernel/sys_arm.c中

本文引用地址:http://www.eepw.com.cn/article/201611/319992.htm

[plain]view plaincopy

print?

  1. asmlinkageintsys_execve(char__user*filenamei,char__user*__user*argv,
  2. char__user*__user*envp,structpt_regs*regs)
  3. {
  4. interror;
  5. char*filename;
  6. filename=getname(filenamei);
  7. error=PTR_ERR(filename);
  8. if(IS_ERR(filename))
  9. gotoout;
  10. error=do_execve(filename,argv,envp,regs);
  11. putname(filename);
  12. out:
  13. returnerror;
  14. }
IS_ERR宏定義在include/linux/err.h,如下所示:

[plain]view plaincopy

print?

  1. #ifndef_LINUX_ERR_H
  2. #define_LINUX_ERR_H
  3. #include
  4. #include
  5. /*
  6. *Kernelpointershaveredundantinformation,sowecanusea
  7. *schemewherewecanreturneitheranerrorcodeoradentry
  8. *pointerwiththesamereturnvalue.
  9. *
  10. *Thisshouldbeaper-architecturething,toallowdifferent
  11. *errorandpointerdecisions.
  12. */
  13. #defineIS_ERR_VALUE(x)unlikely((x)>(unsignedlong)-1000L)
  14. staticinlinevoid*ERR_PTR(longerror)
  15. {
  16. return(void*)error;
  17. }
  18. staticinlinelongPTR_ERR(constvoid*ptr)
  19. {
  20. return(long)ptr;
  21. }
  22. staticinlinelongIS_ERR(constvoid*ptr)
  23. {
  24. returnIS_ERR_VALUE((unsignedlong)ptr);
  25. }
  26. #endif/*_LINUX_ERR_H*/
下面我們就來具體分析一下這段代碼,看看內核中的巧妙設計思路。

要想明白IS_ERR(),首先理解要內核空間。所有的驅動程序都是運行在內核空間,內核空間雖然很大,但總是有限的,而在這有限的空間中,其最後一個page是專門保留的,也就是說一般人不可能用到內核空間最後一個page的指針。換句話說,你在寫設備驅動程序的過程中,涉及到的任何一個指針,必然有三種情況:

  1. 有效指針;
  2. NULL,空指針;
  3. 錯誤指針,或者說無效指針。

而所謂的錯誤指針就是指其已經到達了最後一個page,即內核用最後一頁捕捉錯誤。比如對於32bit的系統來說,內核空間最高地址0xffffffff,那麼最後一個page就是指的0xfffff000~0xffffffff(假設4k一個page),這段地址是被保留的。內核空間為什麼留出最後一個page?我們知道一個page可能是4k,也可能是更多,比如8k,但至少它也是4k,所以留出一個page出來就可以讓我們把內核空間的指針來記錄錯誤了。內核返回的指針一般是指向頁面的邊界(4k邊界),即ptr & 0xfff == 0。如果你發現你的一個指針指向這個範圍中的某個地址,那麼你的代碼肯定出錯了。IS_ERR()就是判斷指針是否有錯,如果指針並不是指向最後一個page,那麼沒有問題;如果指針指向了最後一個page,那麼說明實際上這不是一個有效的指針,這個指針裡保存的實際上是一種錯誤代碼。而通常很常用的方法就是先用IS_ERR()來判斷是否是錯誤,然後如果是,那麼就調用PTR_ERR()來返回這個錯誤代碼。因此,判斷一個指針是不是有效的,可用如下的方式:

#define IS_ERR_VALUE(x) unlikely((x) > (unsigned long)-1000L)

(unsigned long)-1000L 應該為 (unsigned long)-0x1000L!(因為 -0x1000 才是 0xFFFFF000),這應該是內核的一個bug吧!在2.6.30.4的內核中是這樣定義的:

[plain]view plaincopy

print?

  1. #defineMAX_ERRNO4095
  2. #defineIS_ERR_VALUE(x)unlikely((x)>=(unsignedlong)-MAX_ERRNO)

即判斷是不是在(0xfffff000,0xffffffff)之間,因此,可以用IS_ERR()來判斷內核函數的返回值是不是一個有效的指針。注意這裡用unlikely()的用意!

至於PTR_ERR(), ERR_PTR(),只是強制轉換以下而已。現在應該知道為什麼我寫返回錯誤碼的時候也加個負號如 -ENOSYS這樣子了。而PTR_ERR()只是返回錯誤代碼,也就是提供一個信息給調用者,如果你只需要知道是否出錯,而不在乎因為什麼而出錯,那你當然不用調用PTR_ERR()了。

而我們的錯誤碼的值在內存中定義都是這樣的(asm-generic/errno-base.h):

[plain]view plaincopy

print?

  1. ......
  2. #defineEPERM1/*Operationnotpermitted*/
  3. #defineENOENT2/*Nosuchfileordirectory*/
  4. #defineESRCH3/*Nosuchprocess*/
  5. #defineEINTR4/*Interruptedsystemcall*/
  6. #defineEIO5/*I/Oerror*/
  7. #defineENXIO6/*Nosuchdeviceoraddress*/
  8. #defineE2BIG7/*Argumentlisttoolong*/
  9. #defineENOEXEC8/*Execformaterror*/
  10. #defineEBADF9/*Badfilenumber*/
  11. #defineECHILD10/*Nochildprocesses*/
  12. #defineEAGAIN11/*Tryagain*/
  13. #defineENOMEM12/*Outofmemory*/
  14. #defineEACCES13/*Permissiondenied*/
  15. #defineEFAULT14/*Badaddress*/
  16. #defineENOTBLK15/*Blockdevicerequired*/
  17. #defineEBUSY16/*Deviceorresourcebusy*/
  18. #defineEEXIST17/*Fileexists*/
  19. #defineEXDEV18/*Cross-devicelink*/
  20. #defineENODEV19/*Nosuchdevice*/
  21. #defineENOTDIR20/*Notadirectory*/
  22. #defineEISDIR21/*Isadirectory*/
  23. #defineEINVAL22/*Invalidargument*/
  24. #defineENFILE23/*Filetableoverflow*/
  25. #defineEMFILE24/*Toomanyopenfiles*/
  26. #defineENOTTY25/*Notatypewriter*/
  27. #defineETXTBSY26/*Textfilebusy*/
  28. #defineEFBIG27/*Filetoolarge*/
  29. #defineENOSPC28/*Nospaceleftondevice*/
  30. #defineESPIPE29/*Illegalseek*/
  31. #defineEROFS30/*Read-onlyfilesystem*/
  32. #defineEMLINK31/*Toomanylinks*/
  33. #defineEPIPE32/*Brokenpipe*/
  34. #defineEDOM33/*Mathargumentoutofdomainoffunc*/
  35. #defineERANGE34/*Mathresultnotrepresentable*/
  36. ........
如果指針指向了最後一個page,那麼說明實際上這不是一個有效的指針。這個指針裡保存的實際上是一種錯誤代碼。而通常很常用的方法就是先用IS_ERR()來判斷是否是錯誤,然後如果是,那麼就調用PTR_ERR()來返回這個錯誤代碼。

相關焦點

  • Linux 內核通知鏈和例程代碼
    為了滿足這個需求,也即是讓某個子系統在發生某個事件時通知其它的子系統,Linux內核提供了通知鏈的機制。通知鍊表只能夠在內核的子系統之間使用,而不能夠在內核與用戶空間之間進行事件的通知。通知鍊表是一個函數鍊表,鍊表上的每一個節點都註冊了一個函數。當某個事情發生時,鍊表上所有節點對應的函數就會被執行。所以對於通知鍊表來說有一個通知方與一個接收方。
  • 當運行 Linux 內核的機器死機時……
    作者 | dog250 責編 | 張文頭圖 | CSDN 下載自視覺中國曾經寫過一個模塊,當運行 Linux 內核的機器死機時,SSH肯定無法登錄了,但只要它還響應中斷,就盡力讓它可以通過網絡帶回一些信息。
  • 淺談分析Arm linux 內核移植及系統初始化的過程二
    的內核線程,繼續初始化。__machine_arch_type是一個全局變量,在linux/boot/decompress/misc.c的解壓縮函數中得以賦值。Start_kernel() 函數負責初始化內核各子系統,最後調用reset_init(),啟動一個叫做init的內核線程,繼續初始化。在init內核線程中,將執行下列 init()函數的程序。Init()函數負責完成根文件系統的掛接、初始化設備驅動程序和啟動用戶空間的init進程等重要工作。
  • Linux內核分析 | CVE-2017-1000112(UDP Fragment Offload)
    底層調用 __ip_flush_pending_frames4.4.0內核下 ip_append_data 源碼分析基本流程對於UDP,分片工作主要在 ip_append_data(ip_apepend_page) 中完成。實際上這是比較複雜的一個函數,我們只挑關鍵的說。
  • Ubuntu中升級Linux內核
    從這段話中所表達出的意思可以了解,Linux Kernel 4.3版本已經開始進行,Linus Torvalds也收到了一些新的請求,但具體如何改進還要進一步研究確定。  ●支持ARCv2和HS38 CPU內核  ●增加了隊列自旋鎖的支持  ●許多其他的改進和驅動更新。
  • 深入作業系統,從內核理解網絡包的接收過程(Linux篇)
    在Linux內核實現中,鏈路層協議靠網卡驅動來實現,內核協議棧來實現網絡層和傳輸層。內核對更上層的應用層提供socket接口來供用戶進程訪問。和我們平時寫代碼的方式不一樣的是,內核是通過註冊的方式來實現的。Linux內核中的fs_initcall和subsys_initcall類似,也是初始化模塊的入口。fs_initcall調用inet_init後開始網絡協議棧註冊。通過inet_init,將這些函數註冊到了inet_protos和ptype_base數據結構中了。
  • Linux 內核學習:環境搭建和內核編譯
    內核學習之一:環境搭建--安裝Debian7.3本系列文章假設讀者已對linux有一定的了解,其實學習linux內核不需要有很深的關於linux的知識,只需要了解以下內容:linux基礎知識及基本shell命令;現代作業系統的基本概念;C語言和gcc基本使用。
  • Linux 系統內核的調試
    以試驗使用的kgdb補丁為例,linux內核的版本為linux-2.6.7,補丁版本為kgdb-2.2。下面的工作在開發機(developement)上進行,以上面介紹的試驗環境為例,某些具體步驟在實際的環境中可能要做適當的改動:I、內核的配置與編譯  [root@lisl tmp]# tar -jxvf linux-2.6.7.tar.bz2  [root@lisl tmp]#tar -jxvf linux-2.6.7-kgdb-2.2.tar.tar
  • Linux內核編譯初體驗
    下載內核在ftp://ftp.kernel.org/pub/linux/kernel/下載原版內核本文引用地址:http://www.eepw.com.cn/article/201611/319326.htm此處使用linux-2.6.22.6.tar.bz22.
  • Linux內核啟動-內核解壓縮
    本文引用地址:http://www.eepw.com.cn/article/148792.htm從內核的生成過程來看內核的連結主要有三步:第一步是把內核的原始碼編譯成.o文件,然後連結,這一步,連結的是arch/i386/kernel/head.S,生成的是vmlinux。
  • linux字符設備驅動基本框架
    1.linux函數調用過程1.1 系統函數調用的意義在Linux的中,有一個思想比較重要:一切皆文件。也就是說,在應用程式中,可以通過open,write,read等函數來操作底層的驅動。2.驅動程序的框架在理解設備框架之前,首先要知道驅動程序主要做了以下幾件事1.將此內核驅動模塊加載到內核中2.從內核中將驅動模塊卸載3.聲明遵循的開源協議2.1 Linux下的設備Linux下分成三大類設備:字符設備:字符設備是能夠像字節流一樣被訪問的設備。一般來說對硬體的IO操作可歸結為字符設備。
  • 「正點原子Linux連載」第五十八章Linux INPUT子系統實驗
    本章我們就來學習一下Linux內核中的input子系統。58.1 input子系統58.1.1input子系統簡介input就是輸入的意思,因此input子系統就是管理輸入的子系統,和pinctrl和gpio子系統一樣,都是Linux內核針對某一類設備而創建的框架。
  • linux內核移植-移植2.6.35.4內核到s3c2440
    本來是想移植最新的內核2.6.39但是總是在編譯快完成的時候報錯,有人說是新的內核對arm平臺的支持不好,所以就降低了一下版本,這裡移植2.6.35.4內核一、準備工作1、下載 解壓內核從官網上下載linux-2.6.35的內核, ftp://ftp.kernel.org/pub/linux/kernel/v2.6/ ,文件不大,約85M。
  • linux配置、編譯內核實用工具
    kbuild的執行過程是:Make從根目錄Makefile開始執行,從中獲得與體系結構無關的變量和依賴關係,並同時從arch/*/Makefile中獲得體系特定的變量等信息,這些信息擴展了根目錄Makefile提供的變量。此時kbuild已經擁有構建內核需要的所有變量和目標。然後,Make進入子目錄,把部分變量傳遞給子目錄Makefile。
  • Linux 內核的測試和調試(6)
    本節介紹在把你的補丁包提交到 Linux 郵箱列表之前,需要做哪些操作。另外我們還會介紹如何把它發送出去。寫好代碼後,編譯它。把 make 過程產生的輸出保存到文檔中,查看新代碼有沒有警告信息。找到所有的警告信息,處理掉。當你的代碼編譯過程沒有任何不正常的輸出,安裝這個內核,然後啟動測試。如果啟動正常,查看 dmesg 裡面有沒於錯誤,與老內核生成的 dmesg 日誌做個比較。
  • Linux內核中的內存管理
    Linux內存訪問限制(僅針對32位系統)默認情況下Linux的內核空間映射到4G虛擬地址的最高1G(即0xC0000000 - 0xFFFFFFF)。在x86中,這個偏移量表示為TASK_SIZE,也表示為PAGE_OFFSET。
  • 嵌入式Linux設備驅動開發之:實驗內容——test驅動
    這個簡單的驅動程序的原始碼如下所示:/*test_drv.c*/#includelinux/module.h>#includelinux/init.h>#includelinux/fs.h>#includelinux/kernel.h>#includelinux/slab.h
  • 深入理解Linux內核鍊表
    前面已經給出了雙循環鍊表的示意圖,它的特點是從任意一個節點出發,沿兩個方向的任何一個,都能找到鍊表中的任意一個數據。如果去掉前驅指針,就是單循環鍊表。在Linux內核中使用了大量的鍊表結構來組織數據,包括設備列表以及各種功能模塊中的數據組織。這些鍊表大多採用在[include/linux/list.h]實現的一個相當精彩的鍊表數據結構。
  • linux-3.18內核系統調用
    否則,如果某個用戶應用試圖調用這些已經被淘汰的系統調用,所得到的結果,比如打開了一個文件,就會與預期完全不同,這將令人感到非常奇怪。其實,sys_ni_syscall中的"ni"即表示"not implemented(沒有實現)"。
  • 改善Linux內核實時性方法的研究與實現
    但是內核中仍有大量的不可搶佔區域, 如由自旋鎖spinlock保護的臨界區,以及一些失效搶佔的臨界區。另一個影響Linux實時性的因素就是關中斷,同步操作中使用的關中斷指令增大了中斷延遲,這樣很多由中斷驅動的實時任務得不到及時的執行,系統的實時性能得不到保障。因此提高Linux的可搶佔性,改進其中斷機制有利於改善內核的實時性。