第六章:進階字元驅動程式操作
這章有以下幾個部分主要内容:
1) 學習實作ioctl系統調用,它是用于裝置控制的公共接口
2) 掌握如何使程序休眠(并喚醒)
3) poll/select
4) 異步通知
5) 定位裝置
6) 裝置檔案的通路控制
以下為第1部分, ioctl的學習。
定義(摘自百度百科):
ioctl是裝置驅動程式中對裝置的I/O通道進行管理的函數。所謂對I/O通道進行管理,就
是對裝置的一些特性進行控制,例如序列槽的傳輸波特率、馬達的轉速等等。他的調用個數
如下:
int ioctl(int fd, int cmd, …);
其中fd就是使用者程式打開裝置時使用open函數傳回的檔案标示符,cmd就是使用者程式對設
備的控制指令,至于後面的省略号,那是一些補充參數,一般最多一個,有或沒有是和
cmd的意義相關的。
ioctl函數是檔案結構中的一個屬性分量(就是在file_operations結構中),就是說如果你的驅動程式提供了對ioctl的支
持,使用者就能在使用者程式中使用ioctl函數控制裝置的I/O通道。
實作操作(部分摘自百度百科):
在驅動程式中實作的ioctl函數體内,實際上是有一個switch{case}結構,每一個case對
應一個指令碼,做出一些相應的操作。怎麼實作這些操作,這是每一個程式員自己的事
情,因為裝置都是特定的,這裡也沒法說。關鍵在于怎麼樣組織指令碼,因為在ioctl中
指令碼是唯一聯系使用者程式指令和驅動程式支援的途徑。
指令碼的組織是有一些講究的,因為我們一定要做到指令和裝置是一一對應的,這樣才不
會将正确的指令發給錯誤的裝置,或是把錯誤的指令發給正确的裝置,或是把錯誤的
指令發給錯誤的裝置。這些錯誤都會導緻不可預料的事情發生,而當程式員發現了這些奇
怪的事情的時候,再來調試程式查找錯誤,那将是非常困難的事情。
是以在Linux核心中是這樣定義一個指令碼的:
____________________________________
| 裝置類型 | 序列号 | 方向 |資料尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|
這樣一來,一個指令就變成了一個整數形式的指令碼。不過指令碼非常的不直覺,是以
Linux Kernel中提供了一些宏,這些宏可根據便于了解的字元串生成指令碼,或是從
指令碼得到一些使用者能了解的字元串以标明這個指令對應的裝置類型、裝置序列号、數
據傳送方向和資料傳輸尺寸。
我們就來看看這些宏吧:定義在include/asm-generic/ioctl.h中(我用的是2.6.32核心)
============================ioctl.h=================================
#ifndef _IOC_SIZEBITS
# define _IOC_SIZEBITS 14
#endif
#ifndef _IOC_DIRBITS
# define _IOC_DIRBITS 2
#endif
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
/*
* Direction bits, which any architecture can choose to override
* before including this file.
*/
#ifndef _IOC_NONE
# define _IOC_NONE 0U
#endif
#ifndef _IOC_WRITE
# define _IOC_WRITE 1U
#endif
#ifndef _IOC_READ
# define _IOC_READ 2U
#endif
#define _IOC(dir,type,nr,size) /
(((dir) << _IOC_DIRSHIFT) | /
((type) << _IOC_TYPESHIFT) | /
((nr) << _IOC_NRSHIFT) | /
((size) << _IOC_SIZESHIFT))
#define _IOC_TYPECHECK(t) (sizeof(t))
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(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)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#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)
================================end of ioctl.h===============================
ioctl其實沒有什麼非常難的東西需要了解,關鍵是了解cmd指令碼是怎麼在使用者程式裡生成
并在驅動程式裡解析的,程式員最主要的工作量在switch{case}結構中,因為對裝置的
I/O控制都是通過這一部分的代碼實作的。
先來看看scull.h中關于ioctl的definition
/*
* Ioctl definitions
*/
/* 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)
/*
* The other entities only have "Tell" and "Query", because they're
* not printed in the book, and there's no need to have all six.
* (The previous stuff was only there to show different ways to do it.
*/
#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14)
/* ... more to come */
#define SCULL_IOC_MAXNR 14
/*
* The ioctl() implementation
*/
int scull_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
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;
switch(cmd) { /*我們寫驅動時的重點就在下邊,根據實際的硬體特性對于我們定義的ioctl指令進行操作。以下隻是針對SCULL所寫的,不解釋。*/
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval == 0)
retval = __put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return tmp;
case SCULL_IOCSQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCTQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_qset = arg;
break;
case SCULL_IOCGQSET:
retval = __put_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCQQSET:
return scull_qset;
case SCULL_IOCXQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
retval = __get_user(scull_qset, (int __user *)arg);
if (retval == 0)
retval = put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
scull_qset = arg;
return tmp;
/*
* The following two change the buffer size for scullpipe.
* The scullpipe device uses this same ioctl method, just to
* write less code. Actually, it's the same driver, isn't it?
*/
case SCULL_P_IOCTSIZE:
scull_p_buffer = arg;
break;
case SCULL_P_IOCQSIZE:
return scull_p_buffer;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
}