ioctl 的使用方法詳細說明與例子

Talking To Device Files
與設備文件對話 (writes and IOCTLs)
設備文件是用來代表相對應的硬件設備。絕大多數的硬件設備是用來進行輸出和輸入操作的,所以在內核中肯定有內核從進程中獲得發送到設備的輸出的機制。這是通過打開一個設備文件然後向其中進行寫操作來實現的,如同對普通文件的寫操作。在下面的的例子中,這是通過 device_write實現的。

但這並不總是夠用。設想你有一個通過串口連接的調制解調器(即使你使用的是內置調制解調器,對於CPU來說同樣也是通過連接在串口上來實現工作的)。通常我們通過打開一個設備文件向調制解調器發送信息(將要通過通信線路傳輸的指令或數據)或讀取信息(從通信線路中返回的響應指令或數據)。但是,我們如何設置同串口對話的速率,也就是向串口傳輸數據的速率這個問題仍然沒有解決。

解決之道是在Unix系統中的函數ioctl(Input Output ConTroL的簡寫)。每個設備可以有自己的ioctl命令,通過讀取ioctl's 可以從進程中向內核發送信息,或寫ioctl's向進程返回信息 [1],或者兩者都是,或都不是。函數ioctl 調用時需要三個參數:合適的設備文件的文件描述符,ioctl號,和一個可以被一個任務使用來傳遞任何東西的long類型的參數[2]

ioctl號是反映主設備號,ioctl的種類,對應的命令和參數類型的數字。它通常是通過在頭文件中宏調用 (_IO, _IOR, _IOW 或_IOWR,取決於其種類)來建立的。該頭文件應該被使用 ioctl的用戶程序包含(這樣它們就可以生成正確的ioctl's)和內核驅動模塊包含(這樣模塊才能理解它)。在下面的例子中,頭文件爲chardev.h,源程序爲ioctl.c。

即使你只想在自己的模塊中使用ioctls,你最好還是接收正式的 ioctl標準,這樣當你意外的使用別人的ioctls, 或別人使用你的時,你會知道有錯誤發生。詳情參見內核代碼目錄樹下的文件 Documentation/ioctl-number.txt.

Example 7-1. chardev.c
CODE:

/*
*  chardev.c - Create an input/output character device
*/

#include <linux/kernel.h>        /* We're doing kernel work */
#include <linux/module.h>        /* Specifically, a module */
#include <linux/fs.h>
#include <asm/uaccess.h>        /* for get_user and put_user */

#include "chardev.h"
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80

/*
* Is the device open right now? Used to prevent
* concurent access into the same device
*/
static int Device_Open = 0;

/*
* The message the device will give when asked
*/
static char Message[BUF_LEN];

/*
* How far did the process reading the message get?
* Useful if the message is larger than the size of the
* buffer we get to fill in device_read.
*/
static char *Message_Ptr;

/*
* This is called whenever a process attempts to open the device file
*/
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk("device_open(%p)\n", file);
#endif

        /*
         * We don't want to talk to two processes at the same time
         */
        if (Device_Open)
                return -EBUSY;

        Device_Open++;
        /*
         * Initialize the message
         */
        Message_Ptr = Message;
        try_module_get(THIS_MODULE);
        return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
        printk("device_release(%p,%p)\n", inode, file);
#endif

        /*
         * We're now ready for our next caller
         */
        Device_Open--;

        module_put(THIS_MODULE);
        return SUCCESS;
}

/*
* This function is called whenever a process which has already opened the
* device file attempts to read from it.
*/
static ssize_t device_read(struct file *file,        /* see include/linux/fs.h   */
                           char __user * buffer,        /* buffer to be
                                                         * filled with data */
                           size_t length,        /* length of the buffer     */
                           loff_t * offset)
{
        /*
         * Number of bytes actually written to the buffer
         */
        int bytes_read = 0;

#ifdef DEBUG
        printk("device_read(%p,%p,%d)\n", file, buffer, length);
#endif

        /*
         * If we're at the end of the message, return 0
         * (which signifies end of file)
         */
        if (*Message_Ptr == 0)
                return 0;

        /*
         * Actually put the data into the buffer
         */
        while (length && *Message_Ptr) {

                /*
                 * Because the buffer is in the user data segment,
                 * not the kernel data segment, assignment wouldn't
                 * work. Instead, we have to use put_user which
                 * copies data from the kernel data segment to the
                 * user data segment.
                 */
                put_user(*(Message_Ptr++), buffer++);
                length--;
                bytes_read++;
        }

#ifdef DEBUG
        printk("Read %d bytes, %d left\n", bytes_read, length);
#endif

        /*
         * Read functions are supposed to return the number
         * of bytes actually inserted into the buffer
         */
        return bytes_read;
}

/*
* This function is called when somebody tries to
* write into our device file.
*/
static ssize_t
device_write(struct file *file,
             const char __user * buffer, size_t length, loff_t * offset)
{
        int i;

#ifdef DEBUG
        printk("device_write(%p,%s,%d)", file, buffer, length);
#endif

        for (i = 0; i < length && i < BUF_LEN; i++)
                get_user(Message, buffer + i);

        Message_Ptr = Message;

        /*
         * Again, return the number of input characters used
         */
        return i;
}

/*
* This function is called whenever a process tries to do an ioctl on our
* device file. We get two extra parameters (additional to the inode and file
* structures, which all device functions get): the number of the ioctl called
* and the parameter given to the ioctl function.
*
* If the ioctl is write or read/write (meaning output is returned to the
* calling process), the ioctl call returns the output of this function.
*
*/
int device_ioctl(struct inode *inode,        /* see include/linux/fs.h */
                 struct file *file,        /* ditto */
                 unsigned int ioctl_num,        /* number and param for ioctl */
                 unsigned long ioctl_param)
{
        int i;
        char *temp;
        char ch;

        /*
         * Switch according to the ioctl called
         */
        switch (ioctl_num) {
        case IOCTL_SET_MSG:
                /*
                 * Receive a pointer to a message (in user space) and set that
                 * to be the device's message.  Get the parameter given to
                 * ioctl by the process.
                 */
                temp = (char *)ioctl_param;

                /*
                 * Find the length of the message
                 */
                get_user(ch, temp);
                for (i = 0; ch && i < BUF_LEN; i++, temp++)
                        get_user(ch, temp);

                device_write(file, (char *)ioctl_param, i, 0);
                break;

        case IOCTL_GET_MSG:
                /*
                 * Give the current message to the calling process -
                 * the parameter we got is a pointer, fill it.
                 */
                i = device_read(file, (char *)ioctl_param, 99, 0);

                /*
                 * Put a zero at the end of the buffer, so it will be
                 * properly terminated
                 */
                put_user('\0', (char *)ioctl_param + i);
                break;

        case IOCTL_GET_NTH_BYTE:
                /*
                 * This ioctl is both input (ioctl_param) and
                 * output (the return value of this function)
                 */
                return Message[ioctl_param];
                break;
        }

        return SUCCESS;
}

/* Module Declarations */

/*
* This structure will hold the functions to be called
* when a process does something to the device we
* created. Since a pointer to this structure is kept in
* the devices table, it can't be local to
* init_module. NULL is for unimplemented functions.
*/
struct file_operations Fops = {
        .read = device_read,
        .write = device_write,
        .ioctl = device_ioctl,
        .open = device_open,
        .release = device_release,        /* a.k.a. close */
};

/*
* Initialize the module - Register the character device
*/
int init_module()
{
        int ret_val;
        /*
         * Register the character device (atleast try)
         */
        ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

        /*
         * Negative values signify an error
         */
        if (ret_val < 0) {
                printk("%s failed with %d\n",
                       "Sorry, registering the character device ", ret_val);
                return ret_val;
        }

        printk("%s The major device number is %d.\n",
               "Registeration is a success", MAJOR_NUM);
        printk("If you want to talk to the device driver,\n");
        printk("you'll have to create a device file. \n");
        printk("We suggest you use:\n");
        printk("mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);
        printk("The device file name is important, because\n");
        printk("the ioctl program assumes that's the\n");
        printk("file you'll use.\n");

        return 0;
}

/*
* Cleanup - unregister the appropriate file from /proc
*/
void cleanup_module()
{
        int ret;

        /*
         * Unregister the device
         */
        ret = unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

        /*
         * If there's an error, report it
         */
        if (ret < 0)
                printk("Error in module_unregister_chrdev: %d\n", ret);
}


Example 7-2. chardev.h

CODE:
/*
*  chardev.h - the header file with the ioctl definitions.
*
*  The declarations here have to be in a header file, because
*  they need to be known both to the kernel module
*  (in chardev.c) and the process calling ioctl (ioctl.c)
*/

#ifndef CHARDEV_H
#define CHARDEV_H

#include <linux/ioctl.h>

/*
* The major device number. We can't rely on dynamic
* registration any more, because ioctls need to know
* it.
*/
#define MAJOR_NUM 100

/*
* Set the message of the device driver
*/
#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)
/*
* _IOR means that we're creating an ioctl command
* number for passing information from a user process
* to the kernel module.
*
* The first arguments, MAJOR_NUM, is the major device
* number we're using.
*
* The second argument is the number of the command
* (there could be several with different meanings).
*
* The third argument is the type we want to get from
* the process to the kernel.
*/

/*
* Get the message of the device driver
*/
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)
/*
* This IOCTL is used for output, to get the message
* of the device driver. However, we still need the
* buffer to place the message in to be input,
* as it is allocated by the process.
*/

/*
* Get the n'th byte of the message
*/
#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)
/*
* The IOCTL is used for both input and output. It
* receives from the user a number, n, and returns
* Message[n].
*/

/*
* The name of the device file
*/
#define DEVICE_FILE_NAME "char_dev"

#endif


Example 7-3. ioctl.c

CODE:
/*
*  ioctl.c - the process to use ioctl's to control the kernel module
*
*  Until now we could have used cat for input and output.  But now
*  we need to do ioctl's, which require writing our own process.
*/

/*
* device specifics, such as ioctl numbers and the
* major device file.
*/
#include "chardev.h"

#include <fcntl.h>                /* open */
#include <unistd.h>                /* exit */
#include <sys/ioctl.h>                /* ioctl */

/*
* Functions for the ioctl calls
*/

ioctl_set_msg(int file_desc, char *message)
{
        int ret_val;

        ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);

        if (ret_val < 0) {
                printf("ioctl_set_msg failed:%d\n", ret_val);
                exit(-1);
        }
}

ioctl_get_msg(int file_desc)
{
        int ret_val;
        char message[100];

        /*
         * Warning - this is dangerous because we don't tell
         * the kernel how far it's allowed to write, so it
         * might overflow the buffer. In a real production
         * program, we would have used two ioctls - one to tell
         * the kernel the buffer length and another to give
         * it the buffer to fill
         */
        ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);

        if (ret_val < 0) {
                printf("ioctl_get_msg failed:%d\n", ret_val);
                exit(-1);
        }

        printf("get_msg message:%s\n", message);
}

ioctl_get_nth_byte(int file_desc)
{
        int i;
        char c;

        printf("get_nth_byte message:");

        i = 0;
        while (c != 0) {
                c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);

                if (c < 0) {
                        printf
                            ("ioctl_get_nth_byte failed at the %d'th byte:\n",
                             i);
                        exit(-1);
                }

                putchar(c);
        }
        putchar('\n');
}




 

CODE:
/*
* Main - Call the ioctl functions
*/
main()
{
        int file_desc, ret_val;
        char *msg = "Message passed by ioctl\n";

        file_desc = open(DEVICE_FILE_NAME, 0);
        if (file_desc < 0) {
                printf("Can't open device file: %s\n", DEVICE_FILE_NAME);
                exit(-1);
        }

        ioctl_get_nth_byte(file_desc);
        ioctl_get_msg(file_desc);
        ioctl_set_msg(file_desc, msg);

        close(file_desc);
}


Notes
[1] 注意這兒“讀”與“寫”的角色再次翻轉過來,在ioctl's中讀是向內核發送信息, 而寫是從內核獲取信息。

[2] 這樣的表述並不準確。 例如你不能在ioctl中傳遞一個結構體,但你可以通過傳遞指向這個結構體的指針實現。

 

函數名: ioctl

  頭文件:#include<sys/ioctl.h>

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

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

  返回值:成功爲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

  /*

  * Direction bits.

  */

  #define _IOC_NONE 0U //沒有數據傳輸

  #define _IOC_WRITE 1U //向設備寫入數據,驅動程序必須從用戶空間讀入數據

  #define _IOC_READ 2U //從設備中讀取數據,驅動程序必須向用戶空間寫入數據

  #define _IOC(dir,type,nr,size) \

  (((dir) << _IOC_DIRSHIFT) | \

  ((type) << _IOC_TYPESHIFT) | \

  ((nr) << _IOC_NRSHIFT) | \

  ((size) << _IOC_SIZESHIFT))

  /*

  * used to create numbers

  */

  //構造無參數的命令編號

  #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)

  //構造從驅動程序中讀取數據的命令編號

  #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))

  //用於向驅動程序寫入數據命令

  #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))

  //用於雙向傳輸

  #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

  /*

  *used to decode ioctl numbers..

  */

  //從命令參數中解析出數據方向,即寫進還是讀出

  #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)

  //從命令參數中解析出幻數type

  #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)

  //從命令參數中解析出序數number

  #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)

  //從命令參數中解析出用戶數據大小

  #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

  /* ...and for the drivers/sound files... */

  #define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)

  #define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)

  #define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)

  #define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)

  #define IOCSIZE_SHIFT (_IOC_SIZESHIFT)

  程序例:

  #include <stdlib.h>

  #include <stdio.h>

  #include <sys/ioctl.h>

  int main(void) {

  ..int stat;

  /* use func 8 to determine if the default drive is removable */

  ..stat = ioctl(0, 8, 0, 0);

  ..if (!stat)

  ....printf("Drive %c is removable.\n", getdisk() + 'A');

  ..else

  ....printf("Drive %c is not removable.\n", getdisk() + 'A');

  ..return 0;

  }

  int ioctl( int fd, int request, .../* void *arg */ ) 詳解

  第三個參數總是一個指針,但指針的類型依賴於request 參數。我們可以把和網絡相關的請求劃分爲6 類:

  套接口操作

  文件操作

  接口操作

  ARP 高速緩存操作

  路由表操作

  流系統

  下表列出了網絡相關ioctl請求的request 參數以及arg 地址必須指向的數據類型:

  

類別 Request 說明 數據類型


SIOCATMARK
SIOCSPGRP
SIOCGPGRP
是否位於帶外標記
設置套接口的進程ID 或進程組ID
獲取套接口的進程ID 或進程組ID
int
int
int

FIONBIO
FIOASYNC
FIONREAD
FIOSETOWN
FIOGETOWN
設置/ 清除非阻塞I/O 標誌
設置/ 清除信號驅動異步I/O 標誌
獲取接收緩存區中的字節數
設置文件的進程ID 或進程組ID
獲取文件的進程ID 或進程組ID
int
int
int
int
int

SIOCGIFCONF
SIOCSIFADDR
SIOCGIFADDR
SIOCSIFFLAGS
SIOCGIFFLAGS
SIOCSIFDSTADDR
SIOCGIFDSTADDR
SIOCGIFBRDADDR
SIOCSIFBRDADDR
SIOCGIFNETMASK
SIOCSIFNETMASK
SIOCGIFMETRIC
SIOCSIFMETRIC
SIOCGIFMTU
SIOCxxx
獲取所有接口的清單
設置接口地址
獲取接口地址
設置接口標誌
獲取接口標誌
設置點到點地址
獲取點到點地址
獲取廣播地址
設置廣播地址
獲取子網掩碼
設置子網掩碼
獲取接口的測度
設置接口的測度
獲取接口MTU
(還有很多取決於系統的實現)
struct ifconf
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
struct ifreq
ARP SIOCSARP
SIOCGARP
SIOCDARP
創建/ 修改ARP 表項
獲取ARP 表項
刪除ARP 表項
struct arpreq
struct arpreq
struct arpreq

SIOCADDRT
SIOCDELRT
增加路徑
刪除路徑
struct rtentry
struct rtentry
I_xxx    
套接口操作:

  明確用於套接口操作的ioctl請求有三個, 它們都要求ioctl的第三個參數是指向某個整數的一個指針。

  SIOCATMARK: 如果本套接口的的度指針當前位於帶外標記,那就通過由第三個參數指向的整數返回一個非0 值;否則返回一個0 值。POSIX 以函數sockatmark 替換本請求。

  SIOCGPGRP : 通過第三個參數指向的整數返回本套接口的進程ID 或進程組ID ,該ID 指定針對本套接口的SIGIO 或SIGURG 信號的接收進程。本請求和fcntl 的F_GETOWN 命令等效,POSIX 標準化的是fcntl 函數。

  SIOCSPGRP : 把本套接口的進程ID 或者進程組ID 設置成第三個參數指向的整數,該ID 指定針對本套接口的SIGIO 或SIGURG 信號的接收進程,本請求和fcntl 的F_SETOWN 命令等效,POSIX 標準化的是fcntl 操作。

  文件操作:

  以下5 個請求都要求ioctl的第三個參數指向一個整數。

  FIONBIO : 根據ioctl的第三個參數指向一個0 或非0 值分別清除或設置本套接口的非阻塞標誌。本請求和O_NONBLOCK 文件狀態標誌等效,而該標誌通過fcntl 的F_SETFL 命令清除或設置。

  FIOASYNC : 根據iocl 的第三個參數指向一個0 值或非0 值分別清除或設置針對本套接口的信號驅動異步I/O 標誌,它決定是否收取針對本套接口的異步I/O 信號(SIGIO )。本請求和O_ASYNC 文件狀態標誌等效,而該標誌可以通過fcntl 的F_SETFL 命令清除或設置。

  FIONREAD : 通過由ioctl的第三個參數指向的整數返回當前在本套接口接收緩衝區中的字節數。本特性同樣適用於文件,管道和終端。

  FIOSETOWN : 對於套接口和SIOCSPGRP 等效。

  FIOGETOWN : 對於套接口和SIOCGPGRP 等效。

定義

  ioctl是設備驅動程序中對設備的I/O通道進行管理的函數。所謂對I/O通道進行管理,就

  是對設備的一些特性進行控制,例如串口的傳輸波特率、馬達的轉速等等。它的調用個數

  如下:

  int ioctl(int fd, int cmd, …);

  其中fd就是用戶程序打開設備時使用open函數返回的文件標示符,cmd就是用戶程序對設

  備的控制命令,至於後面的省略號,那是一些補充參數,一般最多一個,有或沒有是和

  cmd的意義相關的。

  ioctl函數是文件結構中的一個屬性分量,就是說如果你的驅動程序提供了對ioctl的支

  持,用戶就能在用戶程序中使用ioctl函數控制設備的I/O通道。

必要性

  如果不用IOCTL的話,也能實現對設備I/O通道的控制,但那就是蠻擰了。例如,我們可

  以在驅動程式中實現WRITE的時候檢查一下是否有特別約定的數據流通過,如果有的話,

  那麼後面就跟着控制命令(一般在SOCKET編程中常常這樣做)。不過如果這樣做的話,會

  導致代碼分工不明,程式結構混亂,程式員自己也會頭昏眼花的。

  所以,我們就使用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(int fd,int command, (char*)argstruct)。ioctl調用與網絡編程有關,文件描述符fd實際上是由socket()系統調用返回的。參數command的取值由/usr/include/linux/sockios.h所規定。這些command的由於功能的不同,可分爲以下幾個小類:
• 改變路由表 (例如 SIOCADDRT, SIOCDELRT),
• 讀/更新 ARP/RARP 緩存(如:SIOCDARP, SIOCSRARP),
• 一般的與網絡接口有關的(例如 SIOCGIFNAME, SIOCSIFADDR 等等)
在Gooodies目錄下有很多樣例程序展示瞭如何使用ioctl。當你看這些程序時,注意參數argstruct是與參數command相關的。例如,與路由表相關的ioctl使用rtentry這種結構,rtentry定義在/usr/include/linux/route.h(參見例子adddefault.c)。與ARP有關的ioctl調用使用arpreq結構,arpreq定義在/usr/include/linux/if_arp.h(參見例子arpread.c)
與網絡接口有關的ioctl調用使用的command參數通常看起來像SIOCxIFyyyy的形式,這裏x要麼是S(設定set,寫write),要麼是G(得到get,讀 read)。在getifinfo.c程序中就使用了這種形式的command參數來讀IP地址,硬件地址,廣播地址和得到與網絡接口有關的一些標誌(flag)。在這些ioctl調用中,第三個參數是ifreq結構,它在/usr/include/linux/if.h 中定義。在某些情況下, ioctrl調用可能會使用到在sockios.h之外的新的定義,例如,WaveLAN無線網絡卡會保存有關無線網絡信號強度的信息,這對用戶的程序可能有用。但用戶怎麼得到這種信息呢?我們的第一個本能是在sockios.h中定義新的ioctl命令,例如SIOCGIFWVLNSS(它的英文縮寫表示WaveLAN的信號強度)。但不幸的是,這種命令不是對所有其他的網絡接口(例如:loopback環回接口)有意義,而且不應當允許對於WAVLAN卡以外的網絡接口使用ioctl命令。那麼,我們需要的是這樣一種機制:它能夠定義一種與網絡接口相關的ioctl命令。幸運的是,在Linux操作系統中已經爲實現這個目的內建了一種掛鉤(hook)機制。當你再次看sockios.h文件時,你將發現每一種設備已經預先定義了SIOCDEVPRIVATE的ioctl命令。而它的實現將留給開發相應驅動程序的人去完成。
通常,一個用戶程序使用ioctl(sockid,SIOCDEVPRIVATE,(char*)&ifr)來調用與某種設備(指像WaveLAN那樣的特殊設備)相關的ioctl命令,這裏ifr是struct ifreq ifr形式的變量。用戶程序應當在ifr.ifr_name中填充與這個設備相關的名字,例如,假設WaveLAN使用的接口號爲eth1。一般的,一個用戶程序還需要與內核互相交換ioctl的command參數和結果,這可以通過ifr.ifr_data這個變量來實現,例如,想得到WaveLAN中表示信號強度的信息時,可以通過返回這個變量來實現。Linux的源代碼已經包括了兩種設備de4x5和ewrk3,它們定義並且實現了特定的ioctl調用。這兩個設備的源代碼在de4x5.h,de4x5.c,ewrk3.h,ewrk3.c中(在/usr/src/linux/drivers/net/目錄中)。這兩種設備都定義了它們特有的結構(struct ewrk3_ioctl 和 struct de4x5_ioctl)來方便用戶程序和設備驅動之間交換信息。每次調用ioctl前,用戶程序應當在相應的結構變量中設定合適的初值,並且將ifr.ifr_data指向該值。
在我們進一步討論ewrk3和de4x5的代碼前,讓我們仔細看看ioctl調用是如何一步步地實現的。所有的和接口相關的ioctl請求(SIOCxIFyyyy 和 SIOCDEVPRIVATE)將會調用dev_ioctl()(在/usr/src/linux/net/core/dev.c中)。但這只是一個包裝器(wrapper),實際的動作將由dev_ifsioc()(也在dev.c中)來實現。差不多dev_ioctl()這個函數所做的所有工作只是檢查這個調用是否已經有了正當的權限(例如,改變路由表需要有root的權限)。而dev_ifsioc()這個函數首先要做的一些事情包括得到與ifr.ifr_name相匹配的設備的結構(在/usr/include/linux/netdevice.h 中定義)。但這是在實現特定的接口命令(例如:SIOCGIFADDR)之後。這些特定的接口命令被放置到一個巨大的switch語句之中。其中 SIOCDEVPRIVATE命令和其他的在0x89F0到0x89FF之間的代碼將出現在switch語句中的一個分支——default語句中。內核會檢查表示設備的結構變量中,是否已經定義了一個與設備相關的ioctl句柄(handler)。這裏的句柄是一個函數指針,它在表示設備的結構變量中do_ioctl部分。如果已經設置了這個句柄,那麼內核將會執行它。
所以,如果要實現一個與設備相關的ioctl命令,所要做的只是編寫一個與這個設備相關的ioctl句柄,並且將表示這個設備的結構變量中do_ioctl部分指向這個句柄。對於ewrk3這個設備,它的句柄是ewrk3_ioctl()(在ewrk3.c裏面)並且相應的表示該設備的結構變量由ewrk3_init()來初始化。在ewrk3_ioctl() 的代碼中清晰的指出ifr.ifr_data是用作設備驅動程序和用戶程序之間交換信息的。注意,這部分的內存可以雙向的交流信息。例如,在ewrk3的驅動程序代碼中,if.ifr_data的頭兩個字節是用來表示特殊的動作(例如,EWRK3_SET_PROM,EWRK3_CLR_PROM),而這個動作是符合使用者(驅動程序實現了多個與設備相關的、由SIOCDEVPRIVATE調用的命令)的要求的。另外,ifr.ifr_data中第5個字節指向的緩衝區(buffer)被用來交換其他的信息(如:當使用EWRK3_SET_HWADDR和EWRK3_GET_HWADDR時爲硬件地址)
在你深入ewrk3_ioctl() 時,請注意一般情況下一個用戶進程不能直接訪問內核所在的內存。爲此,驅動開發者可以使用兩個特殊的函數memcpy_tofs()和 memcpy_fromfs()。內核函數memcpy_tofs(arg1, arg2, arg3) 從地址arg2(用戶空間)向地址arg1(內核空間)拷貝arg3個字節。類似的,memcpy_fromfs(arg1,arg2,arg3)從地址 arg2(用戶空間)向地址arg1(內核空間)拷貝arg3個字節。在這些調用之前,verify_area()將會檢查這個進程是否擁有合適的訪問權限。另外,注意使用printk()函數可以輸出debug信息。這個函數與printf()函數類似,但不能處理浮點類型的數。內核代碼不能夠使用 printf()函數。printk()函數產生的結果將記錄在/usr/adm/messages裏。如果想知道更多的關於這些函數的或者與它們相關的信息,可以參考《Linux Kernel Hacker’s Guide》(在Linux文檔網站的首頁) 這本書中Supporting Functions部分。

畢業後頭五年決定你的一生                                       10類最急需IT人才:Java開發者居首       

海量Android教程、開發資料和源碼                         給將成爲“Android高手”的10個建議 

成爲Java高手的25個學習目標--非常經典               Android 4.1果凍豆新特性詳解 

Java侵權訴訟Google獲勝,Android厚積薄發          面試必備:Android筆試總結 

Android高手必須掌握的28大內容和10個建議       Android平臺研發人才缺口30萬 

Android開發環境安裝和配置步驟詳細圖解            2012國內移動App開發者大調查結果 

Windows 7下搭建android開發環境步驟圖解        Android 4.0的30個突出的新特性 

Android高手要經過的6個階段和6個境界               linux下搭建Android開發環境步驟 

從IT菜鳥變爲“IT骨幹開發者”的11個建議          程序員編程技術迅速提高的終極攻略 

2012世界各國人均GDP排名,中國超泰國              2012年全國各省平均工資排行 

2012年中國大學高校排行榜(580強排名)         中國各省市面積和人口數量排名 

中國百萬開發者大調查:程序員的薪水不錯         Java高手需要越過的10座高山

周立功談嵌入式:我的25年嵌入式生涯                Android和Java語言的異同和關係 

華爲中國區手機銷量達千萬,80%爲智能機           谷歌Android碎片化嚴重

2012年中國各省GDP和人均GDP排名                 90後就業“錢景”:IT仍是最佳選擇

2012全球城市競爭力500強,69箇中國城市上榜   不要做浮躁的軟件工程師 

2012年世界500強,79家大陸香港臺灣公司上榜名單 給IT新兵的15個建議 

美國知名科技公司入門級軟件工程師的薪水排名  回顧Java經過的風風雨雨 

71道經典Android面試題和答案--重要知識點都涉及到了 

芯片巨頭海思和展訊:給中國芯片業帶來信心    海量經典Java教程、學習資料和源碼

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章