曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 9030|回复: 2
打印 上一主题 下一主题

ioctl() 函数输入输出应用实例

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-9-27 02:24:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
实验内容
分别在 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 见:http://www.groad.net/bbs/read.php?tid-1212.html

驱动程序代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>

#include <asm/uaccess.h>
#include <asm/io.h>

#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 = 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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

#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;
}

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-9-27 02:40:17 | 只看该作者

应用程序中的ioctl()和驱动程序里的xxx_ioctl()的联系

应用程序中的ioctl()和驱动程序里的xxx_ioctl()的联系如下图所示:

在 ioctl()  中,第 3 个参数是可选的,它的使用根据第 2 个参数来来确定。有些指令(第 2 个参数)需要带第 3 个参数,有些则不需要。如果带有第 3 个参数,则这第 3 个参数的值可以是个整型,也可以是个指针。

在驱动程序里的 ioctl() ,最后一个参数 arg 对应着用户空间的第 3 个参数,它被定义为 unsigned long 类型,而不管用户空间里传递过来的类型是个整型或者是个指针。如果用户空间里并不传递第 3 个参数,那么驱动程序里的接收到的 arg 也是未定义的。因为编译器不会检查额外的参数,所以在编译时并不会发出传递了一个非法的 ioctl 这样的警告,所以与此关联的错误可能变得难以查找。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
板凳
 楼主| 发表于 2009-9-27 16:11:52 | 只看该作者

程序说明

应用程序里,使用 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 作为一个边界。由于别的驱动程序的命令码也会如此构造,其基数最大值或多或少,从而再进一步减少了误用的风险。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-4 02:05 , Processed in 0.072687 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表