Chinaunix首页 | 论坛 | 博客
  • 博客访问: 100184
  • 博文数量: 87
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-20 10:54
文章分类
文章存档

2016年(19)

2015年(2)

2013年(66)

我的朋友

分类: LINUX

2013-12-20 10:57:54

实验内容
分别在 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    0X379

int 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 = 0loop < ctrl_info.sizeloop++)
                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 = 0loop < ctrl_info.sizeloop++)
                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 < 0return 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 $(KDIRSUBDIRS=$(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 = 0cnt < 10cnt++{
            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 作为一个边界。由于别的驱动程序的命令码也会如此构造,其基数最大值或多或少,从而再进一步减少了误用的风险。
阅读(530) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~