(V)將介紹file operations中的ioctl。ioctl的prototype為:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
ioctl藉由cmd來判斷後面所接的參數為何,而早期的ioctl號碼並沒有規則,所以很容易重複,後來為了避免重複,採行編碼方式,將cmd拆成幾個部份,包含:
type:
即magic number,可以根據Document/ioctl/ioctl-number.txt挑選一個。
number:
為sequential number或者稱為ordinal number,讓user自行定義,只要自己不重複即可。
direction:
傳輸的方向,不外乎NONOE/READ/WRITE等等。
size:
即參數的size。
因為ioctl藉由cmd來判斷user想要的指令為何,以及後面所帶的參數為何,所以免不了的就會有一個switch/case來判斷,這也算是ioctl的特色吧。
怎麼定義ioctl的command以及如何解譯ioctl的command,我想直接拿ioctl.h來說明。
#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),(_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)
在定義ioctl的command時,我們會根據資料傳輸的方向使用_IO(不需要傳輸資料)/_IOR(讀取)/_IOW(寫入)/_IOWR(讀寫),type是我們挑選的magic number,nr即number,是流水號,size,就是the size of argument,下面是我們的範例brook_ioctl.h:
#ifndef IOC_BROOK_H
#define IOC_BROOK_H
#define BROOK_IOC_MAGIC 'k'
#define BROOK_IOCSETNUM _IOW(BROOK_IOC_MAGIC, 1, int)
#define BROOK_IOCGETNUM _IOR(BROOK_IOC_MAGIC, 2, int)
#define BROOK_IOCXNUM _IOWR(BROOK_IOC_MAGIC, 3, int)
#define BROOK_IOC_MAXNR 3
#endif
這邊定義三個ioctl的command,分別為設定數值(BROOK_IOCSETNUM),取得數值(BROOK_IOCGETNUM)和交換數值(BROOK_IOCXNUM)。
以下是我的module:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> // chrdev
#include <linux/cdev.h> // cdev_add()/cdev_del()
#include <linux/semaphore.h> // up()/down_interruptible()
#include <asm/uaccess.h> // copy_*_user()
#include "ioc_brook.h"
MODULE_LICENSE("GPL");
#define DEV_BUFSIZE 1024
static int dev_major;
static int dev_minor;
struct cdev *dev_cdevp = NULL;
static int
dev_open(struct inode*, struct file*);
static int
dev_release(struct inode*, struct file*);
static int
dev_ioctl(struct inode*, struct file*, unsigned int, unsigned long);
static void __exit exit_modules(void);
struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = dev_open,
.release = dev_release,
.ioctl = dev_ioctl
};
static int dev_open(struct inode *inode, struct file *filp)
{
printk("%s():\n", __FUNCTION__);
return 0;
}
static int dev_release(struct inode *inode, struct file *filp)
{
printk("%s():\n", __FUNCTION__);
return 0;
}
static int brook_num = 0;
static int
dev_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long args)
{
int tmp, err = 0, ret = 0;
if (_IOC_TYPE(cmd) != BROOK_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > BROOK_IOC_MAXNR)
return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_READ) {
err = !access_ok(VERIFY_WRITE, (void __user*)args, _IOC_SIZE(cmd));
} else if (_IOC_DIR(cmd) & _IOC_WRITE) {
err = !access_ok(VERIFY_READ, (void __user *)args, _IOC_SIZE(cmd));
}
if (err)
return -EFAULT;
switch (cmd) {
case BROOK_IOCSETNUM:
// don't need call access_ok() again. using __get_user().
ret = __get_user(brook_num, (int __user *)args);
printk("%s(): get val = %d\n", __FUNCTION__, brook_num);
break;
case BROOK_IOCGETNUM:
ret = __put_user(brook_num, (int __user *)args);
printk("%s(): set val to %d\n", __FUNCTION__, brook_num);
break;
case BROOK_IOCXNUM:
tmp = brook_num;
ret = __get_user(brook_num, (int __user *)args);
if (!ret) {
ret = __put_user(tmp, (int __user *)args);
}
printk("%s(): change val from %d to %d\n",
__FUNCTION__, tmp, brook_num);
break;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return 0;
}
static int __init init_modules(void)
{
dev_t dev;
int ret;
ret = alloc_chrdev_region(&dev, 0, 1, "brook");
if (ret < 0) {
printk("can't alloc chrdev\n");
return ret;
}
dev_major = MAJOR(dev);
dev_minor = MINOR(dev);
printk("register chrdev(%d,%d)\n", dev_major, dev_minor);
dev_cdevp = kmalloc(sizeof(struct cdev), GFP_KERNEL);
if (dev_cdevp == NULL) {
printk("kmalloc failed\n");
goto failed;
}
cdev_init(dev_cdevp, &dev_fops);
dev_cdevp->owner = THIS_MODULE;
ret = cdev_add(dev_cdevp, MKDEV(dev_major, dev_minor), 1);
if (ret < 0) {
printk("add chr dev failed\n");
goto failed;
}
return 0;
failed:
if (dev_cdevp) {
kfree(dev_cdevp);
dev_cdevp = NULL;
}
return 0;
}
static void __exit exit_modules(void)
{
dev_t dev;
dev = MKDEV(dev_major, dev_minor);
if (dev_cdevp) {
cdev_del(dev_cdevp);
kfree(dev_cdevp);
}
unregister_chrdev_region(dev, 1);
printk("unregister chrdev\n");
}
module_init(init_modules);
module_exit(exit_modules);
在dev_ioctl()先檢視command的type(magic number)和number(sequential number)是否正確,接著在根據command的read/write特性,使用access_ok()檢驗該位址是否合法,後面就是ioctl慣有的switch/case了,根據不同的case執行不同的command和解釋後面所攜帶的參數。
底下是我的application:
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "ioc_brook.h"
int main(int argc, char *argv[])
{
int fd, ret;
if (argc < 2) {
printf("Usage: prog \n");
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("open %s failed\n", argv[1]);
return -1;
}
ret = 10;
if (ioctl(fd, BROOK_IOCSETNUM, &ret) < 0) {
printf("set num failed\n");
return -1;
}
if (ioctl(fd, BROOK_IOCGETNUM, &ret) < 0) {
printf("get num failed\n");
return -1;
}
printf("get value = %d\n", ret);
ret = 100;
if (ioctl(fd, BROOK_IOCXNUM, &ret) < 0) {
printf("exchange num failed\n");
return -1;
}
printf("get value = %d\n", ret);
return 0;
}
在app.c中,將開啟上面註冊的device,並且設定數值(BROOK_IOCSETNUM),讀取數值(BROOK_IOCGETNUM),和交換數值(BROOK_IOCXNUM)。