实验内容:分别在 PC 并口的 13 脚和 25 脚引出两条引线,LED 灯长脚(+)接 PC 并口第 2 脚,短脚(-)接 PC 并口 18 脚 。当加载驱动程序后,启动测试应用程序,一开始会启动开启 LED 然后弹出等待输入信息并等待输入。这时接触两根金属引脚,1s 后 LED 关闭,然后再弹出第二次等待输入信息。再一次接触金属引脚,LED 灯以 0.5s 为周期开关 5 次。接着,弹出第三次等待输入信息,在金属引脚接触后,LED 以 0.2s 为间隔开关。此时,如果再次接触金属引线,那么程序终止。实验目的主要是熟悉 ioctl() 函数的使用方法以及相关宏的使用。代码由 3 部分组成,一个公用头文件,一个设备驱程序,一个应用测试程序。头文件 ioctl_test.h 内容:#ifndef _IOCTLTEST_H_
#define _IOCTLTEST_H_
#define IOCTLTEST_MAGIC 't'
typedef struct {
unsigned long size;
unsigned char buff [128];
}__attribute__((packed)) ioctl_test_info;
#define IOCTLTEST_LEDOFF _IO (IOCTLTEST_MAGIC, 0)
#define IOCTLTEST_LEDON _IO (IOCTLTEST_MAGIC, 1)
#define IOCTLTEST_GETSTATE _IO (IOCTLTEST_MAGIC, 2)
#define IOCTLTEST_READ _IOR (IOCTLTEST_MAGIC, 3, ioctl_test_info)
#define IOCTLTEST_WRITE _IOW (IOCTLTEST_MAGIC, 4, ioctl_test_info)
#define IOCTLTEST_WRITE_READ _IOWR (IOCTLTEST_MAGIC, 5, ioctl_test_info)
#define IOCTLTEST_MAXNR 6
#endif
上面所用到的宏 _IO, _IOR, _IOW, IOWR 见:驱动程序代码:#include #include #include #include #include #include #include #include #include #include "ioctl_test.h"#define IOCTLTEST_DEV_NAME "ioctldev"#define IOCTLTEST_DEV_MAJOR 240#define IOCTLTEST_WRITE_ADDR 0x378#define IOCTLTEST_READ_ADDR 0X379int ioctltest_open (
struct inode *inode, struct file *filp)
{ return 0;
}int ioctltest_release (
struct inode *inode, struct file *filp)
{ return 0;
}int ioctltest_ioctl (
struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{ ioctl_test_info ctrl_info;
int err, size;
int loop;
if (
_IOC_TYPE(
cmd)
!= IOCTLTEST_MAGIC)
/*魔数不一致则出错*/ return -EINVAL;
if (
_IOC_NR(
cmd)
>= IOCTLTEST_MAXNR)
/*魔数相同时判断命令的基数是否大于定义的值*/ return -EINVAL;
size = _IOC_SIZE (
cmd);
if (
size)
{ err = 0;
if (
_IOC_DIR (
cmd)
& _IOC_READ )
err = access_ok (
VERIFY_WRITE, (
void *)
arg, size);
else if (
_IOC_DIR (
cmd)
& _IOC_WRITE)
err = access_ok (
VERIFY_READ, (
void *)
arg, size);
if (
!err)
return err;
} switch (
cmd)
{ case IOCTLTEST_LEDOFF: outb (
0x00, IOCTLTEST_WRITE_ADDR);
break;
case IOCTLTEST_LEDON: outb (
0xff, IOCTLTEST_WRITE_ADDR);
break;
case IOCTLTEST_GETSTATE: return (
inb (
IOCTLTEST_READ_ADDR));
case IOCTLTEST_READ: ctrl_info.buff[0] = inb (
IOCTLTEST_READ_ADDR);
ctrl_info.size = 1;
copy_to_user ((
void *)
arg, (
const void *)
&ctrl_info, (
unsigned long)
size);
break;
case IOCTLTEST_WRITE: copy_from_user ((
void *)
&ctrl_info, (
const void *)
arg, size);
for (
loop = 0;
loop < ctrl_info.size;
loop++)
outb (
ctrl_info.buff[loop], IOCTLTEST_WRITE_ADDR);
break;
case IOCTLTEST_WRITE_READ: copy_from_user ((
void *)
&ctrl_info, (
const void *)
arg, size);
for (
loop = 0;
loop < ctrl_info.size;
loop++)
outb (
ctrl_info.buff[loop], IOCTLTEST_WRITE_ADDR);
ctrl_info.buff[0] = inb (
IOCTLTEST_READ_ADDR);
ctrl_info.size = 1;
copy_to_user ((
void *)
arg, (
const void *)
&ctrl_info, (
unsigned long)
size);
break;
} return 0;
}struct file_operations ioctltest_fops = { .owner = THIS_MODULE, .ioctl = ioctltest_ioctl, .open = ioctltest_open, .release = ioctltest_release,};int ioctltest_init (
void)
{ int result;
result = register_chrdev (
IOCTLTEST_DEV_MAJOR, IOCTLTEST_DEV_NAME, &ioctltest_fops);
if (
result < 0)
return result;
return 0;
}void ioctltest_exit (
void)
{ unregister_chrdev (
IOCTLTEST_DEV_MAJOR, IOCTLTEST_DEV_NAME);
}module_init (
ioctltest_init);
module_exit (
ioctltest_exit);
MODULE_LICENSE (
"Dual BSD/GPL");
Makefile 文件:obj-m := ioctl_dev.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
rm -rf *.ko
rm -rf *.mod.*
rm -rf .*.cmd
rm -rf *.o
应用程序代码:#include
#include
#include
#include
#include
#include
#include
#include "ioctl_test.h"
#define DEVICE_FILENAME "/dev/ioctl_dev"
int main()
{
ioctl_test_info info;
int dev;
int state;
int cnt;
dev = open (DEVICE_FILENAME, O_RDWR | O_NDELAY);
if (dev >= 0) {
printf ("wait...input\n");
ioctl (dev, IOCTLTEST_LEDON);
while (1) {
state = ioctl (dev, IOCTLTEST_GETSTATE);
if (!(state & 0x10)) break;
}
sleep (1);
ioctl (dev, IOCTLTEST_LEDOFF);
printf ("wait... input\n");
while (1) {
info.size = 0;
ioctl (dev, IOCTLTEST_READ, &info);
if (info.size > 0) {
if (!(info.buff[0] & 0x10)) break;
}
}
printf ("IOCTLTEST_READ OK!\n");
info.size = 1;
info.buff[0] = 0xFF;
for (cnt = 0; cnt < 10; cnt++) {
ioctl (dev, IOCTLTEST_WRITE, &info);
info.buff[0] = ~info.buff[0];
usleep (500000);
}
printf ("wait... input\n");
cnt = 0;
state = 0xFF;
while (1) {
info.size = 1;
info.buff[0] = state;
ioctl (dev, IOCTLTEST_WRITE_READ, &info);
if (info.size > 0) {
if (!(info.buff[0] & 0x10)) break;
}
cnt++;
if (cnt >= 2) {
cnt = 0;
state = ~state;
}
usleep (100000);
}
ioctl (dev, IOCTLTEST_LEDOFF);
close (dev);
}
return 0;
}
应用程序中的ioctl()和驱动程序里的xxx_ioctl()的联系如下图所示:在 ioctl() 中,第 3 个参数是可选的,它的使用根据第 2 个参数来来确定。有些指令(第 2 个参数)需要带第 3 个参数,有些则不需要。如果带有第 3 个参数,则这第 3 个参数的值可以是个整型,也可以是个指针。在驱动程序里的 ioctl() ,最后一个参数 arg 对应着用户空间的第 3 个参数,它被定义为 unsigned long 类型,而不管用户空间里传递过来的类型是个整型或者是个指针。如果用户空间里并不传递第 3 个参数,那么驱动程序里的接收到的 arg 也是未定义的。因为编译器不会检查额外的参数,所以在编译时并不会发出传递了一个非法的 ioctl 这样的警告,所以与此关联的错误可能变得难以查找。
应用程序里,使用 open() 成功打开设备后,则通过 ioctl (dev, IOCTLTEST_LEDON); 函数启动 LED 灯。ioctl (dev, IOCTLTEST_LEDON); 最终是通过调用了驱动程序里的 outb (0xff, IOCTLTEST_WRITE_ADDR);实现。由于 IOCTEST_LEDON是通过 _IO (IOCTLTEST_MAGIC, 0) 来实现,所以 _IO 宏所生成的命令里并没有含有数据长度这一项(驱动程序里的 size),因此,在驱动程序里会直接调到 switch(cmd) 比较,最后调用了outb (0xff, IOCTLTEST_WRITE_ADDR);在 LED 常亮时,应用程序会输出 "wait...input" 提示,等待第 13 脚的输入。如果没有输入,那么应用程序会在:while (1) {
state = ioctl (dev, IOCTLTEST_GETSTATE);
if (!(state & 0x10)) break;
}
这里一直循环。在循环里,不断的读取设备的状态 -- 通过 ioctl (dev, IOCTLTEST_GETSTATE); 。这里 state 变量的值会通过设备驱动程序里的: return (inb (IOCTLTEST_READ_ADDR));返回。这时,如果 PC 并口上的两根金属引线接触了,那么 13 脚得到输入低点平,应用程序跳出 while 循环,并睡眠 1s 钟,然后调用 ioctl (dev, IOCTLTEST_LEDOFF); 关闭 LED 灯。 ioctl (dev, IOCTLTEST_LEDOFF); 是最终通过调用设备驱动里的 outb (0x00, IOCTLTEST_WRITE_ADDR); 实现。此时 LED 从亮变为熄灭状态。应用程序又提示输入 "wait ... input" 并再次进入 while 循环:while (1) {
info.size = 0;
ioctl (dev, IOCTLTEST_READ, &info);
if (info.size > 0) {
if (!(info.buff[0] & 0x10)) break;
}
}
在这个 while 循环里,ioctl() 中的第 2 个参数 IOCTLTEST_READ 宏是通过 _IOR (IOCTLTEST_MAGIC, 3, ioctl_test_info)
来定义的。在 _IOR 里使用了第三个参数 ioctl_test_info ,这个参数是个自定义的结构体类型,而利用 _IOR() 生成命令里会有通过计算得到 ioctl_test_info 的大小,并把这个结构体的大小信息嵌入到生成的命令码中。所以,在设备驱动程序里的 ioctl() 中会进入到:if (size) {
err = 0;
if (_IOC_DIR (cmd) & _IOC_READ )
err = access_ok (VERIFY_WRITE, (void *)arg, size);
else if (_IOC_DIR (cmd) & _IOC_WRITE)
err = access_ok (VERIFY_READ, (void *) arg, size);
if (!err)
return err;
}
这个判断里。在这个判断里,_IOC_DIR 宏判断用户传过来的命令是读操作还是写操作。不管是读或写,都会调用 access_ok 函数来判断用户空间的内存块是否可用。(注,此程序代码来自韩国人写的《Linux 设备驱动技术》一书,书中原来是通过 verify_area() 函数来判断用户空间(作者使用的是2.6.4的内核),但这个函数在较新的 2.6.x 内核中已经废弃不用,而改使用 access_ok 函数,所以我这里也对此做了相应的更改(我的内核版本是 2.6.27) )。如果用户空间内存块并不有效,那么返回的 err 为 0 ,这样就不会再往下执行 ()。另外,ioctl (dev, IOCTLTEST_READ, &info);l 还用了第 3 个参数,它是一个 ioctl_test_info 类型结构变量的地址,这也会传递到设备驱动程序中。而在设备驱动程序里,会根据实际情况对这个结构体中的变量进行修改,修改完后通过 copy_to_user() 函数把结果反送回用户应用程序。同理分析 ioctl (dev, IOCTLTEST_WRITE, &info) 和 ioctl (dev, IOCTLTEST_WRITE_READ, &info) 。两种情况。此外,在设备驱动程序里的 ioctl() 中,一开始有一个魔数的判断:if (_IOC_TYPE(cmd) != IOCTLTEST_MAGIC) /*魔数不一致则出错*/
return -EINVAL;
因为设备驱动程序有很多,每个设备驱动程序可能都有着属于自己的魔数,只有经过这样的判断后,才不会一开始就产生误用的情况。但驱动程序的魔数并不是要求每个都是要独一无二的,而是有可能是重复的。所以,如果魔数相同了,可以接着判断自定义的基数值:if (_IOC_NR(cmd) >= IOCTLTEST_MAXNR) /*魔数相同时判断命令的基数是否大于定义的值*/
return -EINVAL;
这个基数值 IOCTLTEST_MAXNR 是自定义的。按照一般的习惯,利用诸如 _IO, _IOR 相关宏来生成命令码时,每个命令码对应的基数的值会从 0 依次递增,比如在上面程序里的头文件里所定义的,命令码中基数最大的值为 5 。所以,最后再定义一个比 5 大的值 6 作为一个边界。由于别的驱动程序的命令码也会如此构造,其基数最大值或多或少,从而再进一步减少了误用的风险。
阅读(549) | 评论(0) | 转发(0) |