【Linux 驅動】第六章 進階字元驅動程式操作 —-ioctl

來源:互聯網
上載者:User

一、ioctl

       1)概念:#include<sys/ioctl.h>

       2)功 能: 控制I/O裝置 ,提供了一種獲得裝置資訊和向裝置發送控制參數的手段。用於向裝置發控制和配置命令 ,有些命令需要控制參數,這些資料是不能用read / write 讀寫的,稱為Out-of-band資料。也就是說,read / write 讀寫的資料是in-band資料,是I/O操作的主體,而ioctl
命令傳送的是控制資訊,其中的資料是輔助的資料。

    3)用 法: int ioctl(int handle, int cmd,[int *argdx, int argcx]);

      4)傳回值:成功為0,出錯為-1

  usr/include/asm-generic/ioctl.h中定義的宏的注釋:

  #define _IOC_NRBITS 8 //序數(number)欄位的字位寬度,8bits

  #define _IOC_TYPEBITS 8 //幻數(type)欄位的字位寬度,8bits

  #define _IOC_SIZEBITS 14 //大小(size)欄位的字位寬度,14bits

  #define _IOC_DIRBITS 2 //方向(direction)欄位的字位寬度,2bits

  #define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) //序數欄位的掩碼,0x000000FF

  #define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) //幻數欄位的掩碼,0x000000FF

  #define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) //大小欄位的掩碼,0x00003FFF

  #define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) //方向欄位的掩碼,0x00000003

  #define _IOC_NRSHIFT 0 //序數欄位在整個欄位中的位移,0

  #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //幻數欄位的位移,8

  #define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //大小欄位的位移,16

  #define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //方向欄位的位移,30

        5)ioctl 系統調用原型
           int    (*ioctl) (struct inode *inode, struct file *filp,unsigned  int cmd, unsigned long arg);
          需要注意的是:不管可選的參數arg是否由使用者給定為一個整數或一個指標,它都以一個unsigned long的形式傳遞。如果調用程式不傳遞arg參數, 被驅動收到的 arg 值是未定義的。因為在arg參數上的類型檢查被關閉了,所以若一個非法參數傳遞給 ioctl,編譯器是無法警示的,且任何關聯的錯誤難以尋找.


二,選擇ioctl命令

        在編寫ioctl代碼前,需要選擇對應不同命令的編號,多數程式員選擇從0開始的一組小編號。為了防止向錯誤的裝置使用正確的命令,命令號應該在系統範圍內唯一。為方便程式員建立唯一的 ioctl 命令代號, 每個命令號被劃分為多個位欄位。要按 Linux 核心的約定方法為驅動選擇 ioctl 的命令號, 應該首先看看 include/asm/ioctl.h
和 Documentation/ioctl-number.txt。 (linux第一個版本使用了一個16位編號,高八位:幻數 低八位:序號)

       要使用的位欄位符號定義在 :

           type(幻數):8 位寬(_IOC_TYPEBITS),參考ioctl-number.txt選擇一個數,並在整個驅動中使用它。
          number(序數):順序編號,8 位寬(_IOC_NRBITS)。
          direction(資料傳送的方向):可能的值是 _IOC_NONE(沒有資料轉送)、_IOC_READ、 _IOC_WRITE和 _IOC_READ|_IOC_WRITE (雙向傳輸資料)。該欄位是一個位元遮罩(兩位), 因此可使用 AND 操作來抽取_IOC_READ 和 _IOC_WRITE。
          size(資料的大小):寬度與體繫結構有關,ARM為14位.可在宏 _IOC_SIZEBITS 中找到特定體系的值. 


    <linux/ipctl.h>中包含<asm/ioctl.h>中包含的 定義了一些構造命令編號的宏: 

          _IO(type,nr)/*沒有參數的命令*/
          _IOR(type, nr, datatype)/*從驅動中讀資料*/
          _IOW(type,nr,datatype)/*寫資料*/
          _IOWR(type,nr,datatype)/*雙向傳送*/
         /*type 和 number 成員作為參數被傳遞, 並且 size 成員通過應用 sizeof 到 datatype 參數而得到*/
         這個標頭檔還定義了用來解開這個欄位的宏:
          _IOC_DIR(nr)
          _IOC_TYPE(nr)
          _IOC_NR(nr)
          _IOC_SIZE(nr)


三,傳回值

        POSIX 標準規定:如果使用了不合適的 ioctl 命令號,應當返回-ENOTTY 。這個錯誤碼被 C 庫解釋為"不合適的裝置 ioctl。然而,它返回-EINVAL仍是相當普遍的。 


四,預定義命令
              有一些ioctl命令是由核心識別的,當這些命令用於自己的裝置時,他們會在我們自己的檔案操作被調用之前被解碼. 因此, 如果你選擇一個ioctl命令編號和系統預定義的相同時,你永遠不會看到該命令的請求,而且因為ioctl 號之間的衝突,應用程式的行為將無法預測。預定義命令分為 3 類:
           (1)用於任何檔案(常規, 裝置, FIFO和socket) 的命令
           (2)只用於常規檔案的命令
           (3)特定於檔案系統類型的命令 
下列 ioctl 命令是預定義給任何檔案,包括裝置特定檔案:
           FIOCLEX :設定 close-on-exec 標誌(File IOctl Close on EXec)。 
           FIONCLEX :清除 close-no-exec 標誌(File IOctl Not CLose on EXec)。 
           FIOQSIZE :這個命令返回一個檔案或者目錄的大小; 當用作一個裝置檔案, 但是, 它返回一個 ENOTTY 錯誤。 
           FIONBIO:"File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一節中描述)。 
五,使用ioctl參數
        在使用ioctl的可選arg參數時,如果傳遞的是一個整數,它可以直接使用。如果是一個指標,就必須小心。當用一個指標引用使用者空間, 我們必須確保使用者地址是有效,其校正(不傳送資料)由函數 access_ok
實現,定義在 : 
                          int access_ok(int   type, const void *addr, unsigned long size); 

                                  type 應當是 VERIFY_READ(讀)或VERIFY_WRITE(讀寫);

                                  addr 參數為使用者空間地址,

                                  size 為位元組數,可使用sizeof()。

                          access_ok 返回一個布爾值: 1 是成功(存取沒問題)和 0 是失敗(存取有問題)。如果它返回假,驅動應當返回 -EFAULT 給調用者。

         注意:首先, access_ok不做校正記憶體存取的完整工作; 它只檢查記憶體引用是否在這個進程有合理許可權的記憶體範圍中,且確保這個地址不指向核心空間記憶體。其次,大部分驅動代碼不需要真正調用 access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它們帶有校正的功能,確保進程能夠寫入給定的記憶體位址,成功時返回 0, 並且在錯誤時返回 -EFAULT.。 

       

<asm/uaccess.h>   

          put_user(datum, ptr) 

          __put_user(datum, ptr) 
          get_user(local, ptr) 
         __get_user(local, ptr) 
       這些宏它們相對copy_to_user 和copy_from_user快, 並且這些宏已被編寫來允許傳遞任何類型的指標,只要它是一個使用者空間地址. 傳送的資料大小依賴 prt 參數的類型, 並且在編譯時間使用 sizeof 和 typeof 等編譯器內建宏確定。他們只傳送1、2、4或8 個位元組。如果使用以上函數來傳送一個大小不適合的值,結果常常是一個來自編譯器的奇怪訊息,如"coversion to non-scalar type requested". 在這些情況中,必須使用 copy_to_user
或者 copy_from_user。 
        __put_user和__get_user 進行更少的檢查(不調用 access_ok), 但是仍然能夠失敗如果被指向的記憶體對使用者是不可寫的,所以他們應只用在記憶體區已經用 access_ok 檢查過的時候。作為通用的規則:當實現一個 read 方法時,調用 __put_user 來節省幾個周期, 或者當你拷貝幾個項時,因此,在第一次資料傳送之前調用 access_ok 一次。


六,權能與受限操作

         Linux 核心提供了一個更加靈活的系統, 稱為權能(capability)。核心專為許可管理上使用權能並匯出了兩個系統調用 capget 和 capset,這樣可以從使用者空間管理權能,其定義在 中。對裝置驅動編寫者有意義的權能如下: 
        CAP_DAC_OVERRIDE /*越過在檔案和目錄上的訪問限制(資料存取控制或 DAC)的能力。*/
        CAP_NET_ADMIN /*進行網路管理工作的能力, 包括那些能夠影響網路介面的任務*/
        CAP_SYS_MODULE /*載入或去除核心模組的能力*/
        CAP_SYS_RAWIO /*進行 "raw"(裸)I/O 操作的能力. 例子包括存取裝置連接埠或者直接和 USB 裝置通訊*/
        CAP_SYS_ADMIN /*截獲的能力, 提供對許多系統管理操作的途徑*/
七,ioctl命令的實現         

switch(cmd){   case MEM_IOCSQUANTUM:retval = _get_user(scull_quantum,(int *)arg);break;   case MEM_IOCGQUANTUM:retval = _put_user(scull_quantum,(int *)arg);break;      default:return -EINVAL;}

八,Linux系統使用ioctl執行個體

         
程式1:檢測介面的 inet_addr,netmask,broad_addr程式2:檢查介面的物理串連是否正常程式3:更簡單一點測試物理串連程式4:調節音量***************************程式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*****************************************************#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*****************************************************#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 0x0000000astruct linktest_value {        unsigned int    cmd;        unsigned int    data;};staticvoidusage(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);}staticintlinktest(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;}intmain(int argc, char *argv[]){        if(argc != 2) {                usage(argv[0]);        }        return linktest(argv[1]);}*************************************程式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 257int 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],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);}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.