# Linux Ioctl internel ioctl函數的作用 特殊的read,write,當你用read,write不能完成某一功能時,就用ioctl 我這裡說的ioctl函數是在驅動程序裡的,因為我不知道還有沒有別的場合用到了ioctl,所以就規定了我們討論的範圍。為什麼要寫篇文章呢,是因為我前一陣子被ioctl給搞混了,這幾天才弄明白它,於是在這裡清理一下頭腦。 # 一、什麼是ioctl。 ioctl是設備驅動程序中對設備的I/O通道進行管理的函數。所謂對I/O通道進行管理,就是對設備的一些特性進行控制,例如串口的傳輸波特率、馬達的轉速等等。它的調用個數如下: ```cpp int ioctl(int fd, ind cmd, …); ``` 其中fd就是用戶程序打開設備時使用open函數返回的文件標示符,cmd就是用戶程序對設備的控制命令,至於後面的省略號,那是一些補充參數,一般最多一個,有或沒有是和cmd的意義相關的。 ioctl函數是文件結構中的一個屬性分量,就是說如果你的驅動程序提供了對ioctl的支持,用戶就可以在用戶程序中使用ioctl函數控制設備的I/O通道。【所以說這些cmd不能和內核中的相同,否則就有衝突了】 # 二、 ioctl的必要性 如果不用ioctl的話,也可以實現對設備I/O通道的控制,但那就是蠻擰了。例如,我們可以在驅動程序中實現write的時候檢查一下是否有特殊約定的數據流通過,如果有的話,那麼後面就跟著控制命令(一般在socket編程中常常這樣做)。但是如果這樣做的話,會導致代碼分工不明,程序結構混亂,程序員自己也會頭昏眼花的。所以,我們就使用ioctl來實現控制的功能。要記住,用戶程序所作的只是通過命令碼告訴驅動程序它想做什麼,至於怎麼解釋這些命令和怎麼實現這些命令,這都是驅動程序要做的事情。 # 三、 ioctl如何實現 這是一個很麻煩的問題,我是能省則省。要說清楚它,沒有四五千字是不行的,所以我這裡是不可能把它說得非常清楚了,不過如果有讀者對用戶程序怎麼和驅動程序聯繫起來感興趣的話,可以看我前一陣子寫的《write的奧秘》。讀者只要把write換成ioctl,就知道用戶程序的ioctl是怎麼和驅動程序中的ioctl實現聯繫在一起的了。 我這裡說一個大概思路,因為我覺得《Linux設備驅動程序》這本書已經說的非常清楚了,但是得化一些時間來看。 在驅動程序中實現的ioctl函數體內,實際上是有一個switch{case}結構,每一個case對應一個命令碼,做出一些相應的操作。怎麼實現這些操作,這是每一個程序員自己的事情,因為設備都是特定的,這裡也沒法說。關鍵在於怎麼樣組織命令碼,因為在ioctl中命令碼是唯一聯繫用戶程序命令和驅動程序支持的途徑。 命令碼的組織是有一些講究的,因為我們一定要做到命令和設備是一一對應的,這樣才不會將正確的命令發給錯誤的設備,或者是把錯誤的命令發給正確的設備,或者是把錯誤的命令發給錯誤的設備。這些錯誤都會導致不可預料的事情發生,而當程序員發現了這些奇怪的事情的時候,再來調試程序查找錯誤,那將是非常困難的事情。 所以在Linux核心中是這樣定義一個命令碼的組織結構: ____________________________________ |設備類型|序列號|方向|數據尺寸| |----------|--------|------|--------| | 8 bit | 8 bit |2 bit |8~14 bit| 這樣一來,一個命令就變成了一個整數形式的命令碼。但是命令碼非常的不直觀,所以Linux Kernel中提供了一些宏,這些宏可根據便於理解的字符串生成命令碼,或者是從命令碼得到一些用戶可以理解的字符串以標明這個命令對應的設備類型、設備序列號、數據傳送方向和數據傳輸尺寸。 這些宏我就不在這裡解釋了,具體的形式請讀者察看Linux核心源代碼中的和,文件裡給除了這些宏完整的定義。 這裡我只多說一個地方,那就是"幻數"。幻數是一個字母,數據長度也是8,所以就用一個特定的字母來標明設備類型,這和用一個數字是一樣的,只是更加利於記憶和理解。就是這樣,再沒有更複雜的了。 更多的說了也沒有,讀者還是看一看源代碼吧,推薦各位閱讀《Linux設備驅動程序》所帶源代碼中的short一例,因為它比較短小,功能比較簡單,可以看明白ioctl的功能和細節。 # 四、 cmd參數如何得出 這裡確實要說一說,cmd參數在用戶程序端由一些宏根據設備類型、序列號、傳送方向、數據尺寸等生成,這個整數通過系統調用傳遞到內核中的驅動程序,再由驅動程序使用解碼宏從這個整數中得到設備的類型、序列號、傳送方向、數據尺寸等信息,然後通過switch{case}結構進行相應的操作。 要透徹理解,只能是通過閱讀源代碼,我這篇文章實際上只是一個引子。Cmd參數的組織還是比較複雜的,我認為要搞熟它還是得花不少時間的,但是這是值得的,驅動程序中最難的是對中斷的理解。 # 五、小結 ioctl其實沒有什麼很難的東西需要理解,關鍵是理解cmd命令碼是怎麼在用戶程序裡生成並在驅動程序裡解析的,程序員最主要的工作量在switch{case}結構中,因為對設備的I/O控制都是通過這一部分的代碼實現的。 # ioctl接口 大部分驅動需要--除了讀寫設備的能力--通過設備驅動進行各種硬件控制的能力.大部分設備可進行超出簡單的數據傳輸之外的操作;用戶空間必須常常能夠請求,例如,設備鎖上它的門,彈出它的介質,報告錯誤信息,改變波特率,或者自我銷毀.這些操作常常通過ioctl方法來支持,它通過相同名子的系統調用來實現. 在用戶空間, ioctl系統調用有下面的原型: ```cpp int ioctl(int fd, unsigned long cmd, ...); ``` 這個原型由於這些點而凸現於Unix系統調用列表,這些點常常表示函數有數目不定的參數.在實際系統中,但是,一個系統調用不能真正有變數目的參數.系統調用必須有一個很好定義的原型,因為用戶程序可存取它們只能通過硬件的"門".因此,原型中的點不表示一個變數目的參數,而是一個單個可選的參數,傳統上標識為char *argp.這些點在那裡只是為了阻止在編譯時的類型檢查.第3個參數的實際特點依賴所發出的特定的控制命令(第2個參數).一些命令不用參數,一些用一個整數值,以及一些使用指向其他數據的指針.使用一個指針是傳遞任意數據到ioctl調用的方法;設備接著可與用戶空間交換任何數量的數據. ioctl調用的非結構化特性使它在內核開發者中失寵.每個ioctl命令,基本上,是一個單獨的,常常無文檔的系統調用,並且沒有方法以任何類型的全面的方式核查這些調用.也難於使非結構化的ioctl參數在所有系統上一致工作;例如,考慮運行在32-位模式的一個用戶進程的64-位系統.結果,有很大的壓力來實現混雜的控制操作,只通過任何其他的方法.可能的選擇包括嵌入命令到數據流(本章稍後我們將討論這個方法)或者使用虛擬文件系統,要么是sysfs要么是設備特定的文件系統. (我們將在14章看看sysfs).但是,事實是ioctl常常是最容易的和最直接的選擇,對於真正的設備操作. ioctl驅動方法有和用戶空間版本不同的原型: ```cpp int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); ``` inode和filp指針是對應應用程序傳遞的文件描述符fd的值,和傳遞給open方法的相同參數. cmd參數從用戶那裡不改變地傳下來,並且可選的參數arg參數以一個unsigned long的形式傳遞,不管它是否由用戶給定為一個整數或一個指針.如果調用程序不傳遞第3個參數,被驅動操作收到的arg值是無定義的.因為類型檢查在這個額外參數上被關閉,編譯器不能警告你如果一個無效的參數被傳遞給ioctl,並且任何關聯的錯誤將難以查找. 如果你可能想到的,大部分ioctl實現包括一個大的switch語句來根據cmd參數,選擇正確的做法.不同的命令有不同的數值,它們常常被給予符號名來簡化編碼.符號名通過一個預處理定義來安排.定制的驅動常常聲明這樣的符號在它們的頭文件中; scull.h為scull聲明它們.用戶程序必須,當然,包含那個頭文件來存取這些符號. # 1.選擇ioctl命令 在為ioctl編寫代碼之前,你需要選擇對應命令的數字.許多程序員的第一個本能的反應是選擇一組小數從0或1開始,並且從此開始向上.但是,有充分的理由不這樣做. ioctl命令數字應當在這個系統是唯一的,為了阻止向錯誤的設備發出正確的命令而引起的錯誤.這樣的不匹配不會不可能發生,並且一個程序可能發現它自己試圖改變一個非串口輸入系統的波特率,例如一個FIFO或者一個音頻設備.如果這樣的ioctl號是唯一的,這個應用程序得到一個EINVAL錯誤而不是繼續做不應當做的事情. 為幫助程序員創建唯一的ioctl命令代碼,這些編碼已被劃分為幾個位段. Linux的第一個版本使用16-位數:高8位是關聯這個設備的"魔"數,低8位是一個順序號,在設備內唯一.這樣做是因為Linus是"無能"的(他自己的話);一個更好的位段劃分僅在後來被設想.不幸的是,許多驅動仍然使用老傳統.它們不得不:改變命令編碼會破壞大量的二進製程序,並且這不是內核開發者願意見到的. 根據Linux內核慣例來為你的驅動選擇ioctl號,你應當首先檢查include/asm/ioctl.h和Documentation/ioctl-number.txt.這個頭文件定義你將使用的位段: type(魔數),序號,傳輸方向,和參數大小. ioctl-number.txt文件列舉了在內核中使用的魔數, [20]因此你將可選擇你自己的魔數並且避免交疊.這個文本文件也列舉了為什麼應當使用慣例的原因. 定義ioctl命令號的正確方法使用4個位段,它們有下列的含義.這個列表中介紹的新符號定義在<linux/ioctl.h>. **type** 魔數.只是選擇一個數(在參考了ioctl-number.txt之後)並且使用它在整個驅動中.這個成員是8位寬(_IOC_TYPEBITS). **number** 序(順序)號.它是8位(_IOC_NRBITS)寬. **direction** 數據傳送的方向,如果這個特殊的命令涉及數據傳送.可能的值是_IOC_NONE(沒有數據傳輸), _IOC_READ, _IOC_WRITE,和_IOC_READ|_IOC_WRITE (數據在2個方向被傳送).數據傳送是從應用程序的觀點來看待的; _IOC_READ意思是從設備讀,因此設備必須寫到用戶空間.注意這個成員是一個位掩碼,因此_IOC_READ和_IOC_WRITE可使用一個邏輯AND操作來抽取. **size** 涉及到的用戶數據的大小.這個成員的寬度是依賴體系的,但是常常是13或者14位.你可為你的特定體系在宏_IOC_SIZEBITS中找到它的值.你使用這個size成員不是強制的-內核不檢查它--但是它是一個好主意.正確使用這個成員可幫助檢測用戶空間程序的錯誤並使你實現向後兼容,如果你曾需要改變相關數據項的大小.如果你需要更大的數據結構,但是,你可忽略這個size成員.我們很快見到如何使用這個成員. 頭文件<asm/ioctl.h>,它包含在<linux/ioctl.h>中,定義宏來幫助建立命令號,如下: ```cpp _IO(type,nr)(給沒有參數的命令) ``` ```cpp _IOR(type, nre, datatype)(給從驅動中讀數據的) ``` ```cpp _IOW(type,nr,datatype)(給寫數據) ``` ```cpp _ IOWR(type,nr,datatype)(給雙向傳送) ``` . type和number成員作為參數被傳遞,並且size成員通過應用sizeof到datatype參數而得到. 這個頭文件還定義宏,可被用在你的驅動中來解碼這個號: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),和_IOC_SIZE(nr).我們不進入任何這些宏的細節,因為頭文件是清楚的,並且在本節稍後有例子代碼展示. 這裡是一些ioctl命令如何在scull被定義的.特別地,這些命令設置和獲得驅動的可配置參數. ```cpp /* Use 'k ' as magic number */ #define SCULL_IOC_MAGIC 'k' /* Please use a different 8-bit number in your code */ #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0) /* * S means "Set" through a ptr, * T means "Tell" directly with the argument value * G means "Get": reply by setting through a pointer * Q means "Query": response is on the return value * X means "eXchange": switch G and S atomically * H means "sHift": switch T and Q atomically */ #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int ) #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) #define SCULL_IOC_MAXNR 14 ``` 真正的源文件定義幾個額外的這裡沒有出現的命令. 我們選擇實現2種方法傳遞整數參數:通過指針和通過明確的值(儘管,由於一個已存在的慣例, ioclt應當通過指針交換值).類似地, 2種方法被用來返回一個整數值:通過指針和通過設置返回值.這個有效只要返回值是一個正的整數;如同你現在所知道的,在從任何系統調用返回時,一個正值被保留(如同我們在read和write中見到的) ,而一個負值被看作一個錯誤並且被用來在用戶空間設置errno.[21] "exchange"和"shift"操作對於scull沒有特別的用處.我們實現"exchange"來顯示驅動如何結合獨立的操作到單個的原子的操作,並且"shift"來連接"tell"和"query".有時需要像這樣的原子的測試-和-設置操作,特別地,當應用程序需要設置和釋放鎖. 命令的明確的序號沒有特別的含義.它只用來區分命令.實際上,你甚至可使用相同的序號給一個讀命令和一個寫命令,因為實際的ioctl號在"方向"位是不同的,但是你沒有理由這樣做.我們選擇在任何地方不使用命令的序號除了聲明中,因此我們不分配一個返回值給它.這就是為什麼明確的號出現在之前給定的定義中.這個例子展示了一個使用命令號的方法,但是你有自由不這樣做. 除了少數幾個預定義的命令(馬上就討論), ioctl的cmd參數的值當前不被內核使用,並且在將來也很不可能.因此,你可以,如果你覺得懶,避免前面展示的複雜的聲明並明確聲明一組調整數字.另一方面,如果你做了,你不會從使用這些位段中獲益,並且你會遇到困難如果你曾提交你的代碼來包含在主線內核中.頭文件<linux/kd.h>是這個老式方法的例子,使用16-位的調整值來定義ioctl命令.那個源代碼依靠調整數因為使用那個時候遵循的慣例,不是由於懶惰.現在改變它可能導致無理由的不兼容. 2.返回值 ioctl的實現常常是一個switch語句,基於命令號.但是當命令號沒有匹配一個有效的操作時缺省的選擇應當是什麼?這個問題是有爭議的.幾個內核函數返回-ENIVAL("Invalid argument"),它有意義是因為命令參數確實不是一個有效的. POSIX標準,但是,說如果一個不合適的ioctl命令被發出,那麼-ENOTTY應當被返回.這個錯誤碼被C庫解釋為"設備的不適當的ioctl",這常常正是程序員需要聽到的.然而,它仍然是相當普遍的來返回-EINVAL,對於響應一個無效的ioctl命令. 3 .預定義的命令 儘管ioctl系統調用最常用來作用於設備,內核能識別幾個命令.注意這些命令,當用到你的設備時,在你自己的文件操作被調用之前被解碼.因此,如果你選擇相同的號給一個你的ioctl命令,你不會看到任何的給那個命令的請求,並且應用程序獲得某些不期望的東西,因為在ioctl號之間的衝突. 預定義命令分為3類: 可對任何文件發出的(常規,設備, FIFO,或者socket)的那些. 只對常規文件發出的那些. 對文件系統類型特殊的那些. 最後一類的命令由宿主文件系統的實現來執行(這是chattr命令如何工作的).設備驅動編寫者只對第一類命令感興趣,它們的魔數是"T".查看其他類的工作留給讀者作為練習; ext2_ioctl是最有趣的函數(並且比預期的要容易理解),因為它實現append-only標誌和immutable標誌. 下列ioctl命令是預定義給任何文件,包括設備特殊的文件: ```cpp FIOCLEX ``` 設置close-on-exec標誌(File IOctl Close on EXec).設置這個標誌使文件描述符被關閉,當調用進程執行一個新程序時. ```cpp FIONCLEX ``` 清除close-no-exec標誌(File IOctl Not CLose on EXec).這個命令恢復普通文件行為,復原上面FIOCLEX所做的. FIOASYNC為這個文件設置或者復位異步通知(如同在本章中"異步通知"一節中討論的).注意直到Linux 2.2.4版本的內核不正確地使用這個命令來修改O_SYNC標誌.因為兩個動作都可通過fcntl來完成,沒有人真正使用FIOASYNC命令,它在這裡出現只是為了完整性. ```cpp FIOQSIZE ``` 這個命令返回一個文件或者目錄的大小;當用作一個設備文件,但是,它返回一個ENOTTY錯誤. ```cpp FIONBIO ``` "File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一節中描述).這個調用修改在filp->f_flags中的O_NONBLOCK標誌.給這個系統調用的第3個參數用作指示是否這個標誌被置位或者清除. (我們將在本章看到這個標誌的角色).注意常用的改變這個標誌的方法是使用fcntl系統調用,使用F_SETFL命令. 列表中的最後一項介紹了一個新的系統調用, fcntl,它看來象ioctl.事實上, fcntl調用非常類似ioctl,它也是獲得一個命令參數和一個額外的(可選地)參數.它保持和ioctl獨立主要是因為歷史原因:當Unix開發者面對控制I/O操作的問題時,他們決定文件和設備是不同的.那時,有ioctl實現的唯一設備是ttys,它解釋了為什麼-ENOTTY是標準的對不正確ioctl命令的回答.事情已經改變,但是fcntl保留為一個獨立的系統調用. # 4.使用ioctl參數 在看scull驅動的ioctl代碼之前,我們需要涉及的另一點是如何使用這個額外的參數.如果它是一個整數,就容易:它可以直接使用.如果它是一個指針,但是,必須小心些. 當用一個指針引用用戶空間,我們必須確保用戶地址是有效的.試圖存取一個沒驗證過的用戶提供的指針可能導致不正確的行為,一個內核oops,系統崩潰,或者安全問題.它是驅動的責任來對每個它使用的用戶空間地址進行正確的檢查,並且返回一個錯誤如果它是無效的. 在第3章,我們看了copy_from_user和copy_to_user函數,它們可用來安全地移動數據到和從用戶空間.這些函數也可用在ioctl方法中,但是ioctl調用常常包含小數據項,可通過其他方法更有效地操作.開始,地址校驗(不傳送數據)由函數access_ok實現,它定義在<asm/uaccess.h>: ```cpp int access_ok(int type, const void *addr, unsigned long size); ``` 第一個參數應當是VERIFY_READ或者VERIFY_WRITE,依據這個要進行的動作是否是讀用戶空間內存區或者寫它. addr參數持有一個用戶空間地址, size是一個字節量.例如,如果ioctl需要從用戶空間讀一個整數, size是sizeof(int).如果你需要讀和寫給定地址,使用VERIFY_WRITE,因為它是VERIRY_READ的超集. 不像大部分的內核函數, access_ok返回一個布爾值: 1是成功(存取沒問題)和0是失敗(存取有問題).如果它返回假,驅動應當返回-EFAULT給調用者. 關於access_ok有多個有趣的東西要注意.首先,它不做校驗內存存取的完整工作;它只檢查看這個內存引用是在這個進程有合理權限的內存範圍中.特別地, access_ok確保這個地址不指向內核空間內存.第2,大部分驅動代碼不需要真正調用access_ok.後面描述的內存存取函數為你負責這個.但是,我們來演示它的使用,以便你可見到它如何完成. scull源碼利用了ioclt號中的位段來檢查參數,在switch之前: ```cpp int err = 0, tmp; int retval = 0; /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; /* * the direction is a bitmask, and VERIFY_WRITE catches R/W * transfers. `Type' is user-oriented, while * access_ok is kernel-oriented, so the concept of "read" and * "write" is reversed */ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; ``` 在調用access_ok之後,驅動可安全地進行真正的傳輸.加上copy_from_user和copy_to_user_函數,程序員可利用一組為被最多使用的數據大小(1, 2, 4,和8字節)而優化過的函數.這些函數在下面列表中描述,它們定義在<asm/uaccess.h>: ```cpp put_user (datum, ptr) __put_user(datum, ptr) ``` 這些宏定義寫datum到用戶空間;它們相對快,並且應當被調用來代替copy_to_user無論何時要傳送單個值時.這些宏已被編寫來允許傳遞任何類型的指針到put_user,只要它是一個用戶空間地址.傳送的數據大小依賴prt參數的類型,並且在編譯時使用sizeof和typeof等編譯器內建宏確定.結果是,如果ptr是一個char指針,傳送一個字節,以及對於2, 4,和可能的8字節. put_user檢查來確保這個進程能夠寫入給定的內存地址.它在成功時返回0,並且在錯誤時返回-EFAULT. __put_user進行更少的檢查(它不調用access_ok),但是仍然能夠失敗如果被指向的內存對用戶是不可寫的.因此, __put_user應當只用在內存區已經用access_ok檢查過的時候. 作為一個通用的規則,當你實現一個read方法時,調用__put_user來節省幾個週期,或者當你拷貝幾個項時,因此,在第一次數據傳送之前調用access_ok一次,如同上面ioctl所示. ```cpp get_user(local, ptr) __get_user(local, ptr) ``` 這些宏定義用來從用戶空間接收單個數據.它們象put_user和__put_user,但是在相反方向傳遞數據.獲取的值存儲於本地變量local;返回值指出這個操作是否成功.再次, __get_user應當只用在已經使用access_ok校驗過的地址. 如果做一個嘗試來使用一個列出的函數來傳送一個不適合特定大小的值,結果常常是一個來自編譯器的奇怪消息,例如"coversion to non-scalar type requested".在這些情況中,必須使用copy_to_user或者copy_from_user. # linux系統ioctl使用示例 程序1:檢測接口的inet_addr, netmask, broad_addr 程序2:檢查接口的物理連接是否正常 程序3:測試物理連接 程序4:調節音量 程序1:檢測接口的inet_addr, netmask, broad_addr ```cpp ***************************程序1******** ******************************** #include <stdio.h> #include <string.h> #include <stdlib .h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl. h> #include <net/if.h> static void usage() { printf("usage : ipconfig interface \n"); exit(0); } int main(int argc,char **argv) { struct sockaddr_in * addr; struct ifreq ifr; char *name,*address; int sockfd; if(argc != 2) usage(); else name = argv[1]; sockfd = socket(AF_INET,SOCK_DGRAM,0); strncpy(ifr. ifr_name,name,IFNAMSIZ-1); if(ioctl(sockfd,SIOCGIFADDR,&ifr) == -1) perror("ioctl error"),exit(1); addr = (struct sockaddr_in *)&(ifr.ifr_addr); address = inet_ntoa(addr->sin_addr); printf("inet addr: %s ",address); if(ioctl(sockfd,SIOCGIFBRDADDR,&ifr) == - 1) perror("ioctl error"),exit(1); addr = (struct sockaddr_in *)&ifr.ifr_broadaddr; address = inet_ntoa(addr->sin_addr); printf("broad addr: %s ",address); if (ioctl(sockfd,SIOCGIFNETMASK,&ifr) == -1) perror("ioctl error"),exit(1); addr = (struct sockaddr_in *)&ifr.ifr_addr; address = inet_ntoa(addr->sin_addr); printf( "inet mask: %s ",address); printf("\n"); exit(0); } ``` 程序2:檢查接口的物理連接是否正常 ```cpp ********************************程序2*************** ************************************** #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net /if.h> #include <stdlib.h> #include <unistd.h> typedef unsigned short u16; typedef unsigned int u32; typedef unsigned char u8; #include <linux/ethtool.h> #include <linux/sockios. h> int detect_mii(int skfd, char *ifname) { struct ifreq ifr; u16 *data, mii_val; unsigned phy_id; /* Get the vitals from the interface. */ strncpy(ifr.ifr_name, ifname, IFNAMSIZ); if ( ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) { fprintf(stderr, "SIOCGMIIPHY on %s failed: %s\n", ifname, strerror(errno)); (void) close(skfd); return 2; } data = (u16 *)(&ifr.ifr_data); phy_id = data[0]; data[1] = 1; if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0) { fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name, strerror( errno)); return 2; } mii_val = data[3]; return(((mii_val & 0x0016) == 0x0004) ? 0 : 1); } int detect_ethtool(int skfd, char *ifname) { struct ifreq ifr; struct ethtool_value edata; memset(&ifr, 0, sizeof(ifr)); edata.cmd = ETHTOOL_GLINK; strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1); ifr.ifr_data = (char *) &edata; if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) { printf("ETHTOOL_GLINK failed: %s\n", strerror(errno)); return 2; } return ( edata.data ? 0 : 1); } int main(int argc, char **argv) { int skfd = -1; char *ifname; int retval; if( argv[1] ) ifname = argv[1]; else ifname = "eth0"; /* Open a socket. */ if (( skfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 ) { printf("socket error\n"); exit(-1); } retval = detect_ethtool(skfd, ifname); if (retval == 2) retval = detect_mii(skfd, ifname); close(skfd); if (retval == 2) printf("Could not determine status\n"); if (retval == 1) printf("Link down\n"); if (retval == 0) printf("Link up\n"); return retval; } ``` 程序3:測試物理連接 ```cpp *******************************程序3***************** ************************************ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <net/if.h> #include <linux/sockios.h> #include <sys/ioctl.h> #define LINKTEST_GLINK 0x0000000a struct linktest_value { unsigned int cmd; unsigned int data; }; static void usage(const char * pname) { fprintf(stderr, "usage:%s <device>\n", pname); fprintf(stderr, "returns: \n"); fprintf(stderr, "\t 0: link detected\n"); fprintf(stderr, "\t%d: %s\n", ENODEV, strerror(ENODEV) ); fprintf(stderr, "\t%d: %s\n", ENONET, strerror(ENONET)); fprintf(stderr, "\t%d: %s\n", EOPNOTSUPP, strerror(EOPNOTSUPP)); exit(EXIT_FAILURE); } static int linktest(const char * devname) { struct ifreq ifr; struct linktest_value edata; int fd; /* setup our control structures. */ memset(&ifr, 0, sizeof(ifr)); strcpy( ifr.ifr_name, devname); /* open control socket. */ fd=socket(AF_INET, SOCK_DGRAM, 0); if(fd < 0 ) return -ECOMM; errno=0; edata.cmd = LINKTEST_GLINK; ifr.ifr_data = (caddr_t)&edata; if(!ioctl(fd, SIOCETHTOOL, &ifr)) { if(edata.data) { fprintf(stdout, "link detected on %s\n", devname); return 0; } else { errno=ENONET; } } perror("linktest"); return errno; } int main(int argc, char *argv[]) { if(argc != 2) { usage(argv[0]); } return linktest(argv[1]); } ``` 程序4:調節音量 ```cpp *************************************程序4** ************************************************** ***** #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/soundcard.h> #include <stdio.h> #include <unistd.h> #include <math.h> #include <string.h> #include <stdlib.h> #define BASE_VALUE 257 int main (int argc,char *argv[]) { int mixer_fd=0; char *names[SOUND_MIXER_NRDEVICES]=SOUND_DEVICE_LABELS; int value,i; printf("\nusage:%s dev_no.[0..24] value[0. .100]\n\n",argv[0]); printf("eg. %s 0 100\n",argv[0]); printf("will change the volume to MAX volume.\n\n" ); printf("The dev_no. are as below:\n"); for (i=0;i<SOUND_MIXER_NRDEVICES;i++) { if (i%3==0) printf("\n"); printf(" %s:%d\t\t",names,i); } printf("\n\n"); if (argc<3) exit(1); if ((mixer_fd = open("/dev/mixer",O_RDWR))) { printf("Mixer opened successfully,working...\n"); value=BASE_VALUE* atoi(argv[2]); if (ioctl(mixer_fd,MIXER_WRITE(atoi(argv[1])),&value)==0) printf("successfully....."); else printf("unsuccessfully.. ..."); printf("done.\n"); } else printf("can't open /dev/mixer error....\n"); exit(0); }