曲径通幽论坛

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

虚拟字符设备实例

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-9-1 16:47:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
代码(代码来自《驱动开发详解》,为了测试,小有修改)
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#define    GLOBALMEM_SIZE    0x1000
#define    MEM_CLEAR    0x01
#define    GLOBALMEM_MAJOR 240

static int globalmem_major = GLOBALMEM_MAJOR;

/*globalmem 设备结构体*/
struct globalmem_dev {
    struct cdev cdev;    /*cdev 结构体*/
    unsigned char mem [GLOBALMEM_SIZE];    /*全局内存*/
};

/*设备结构体指针*/
struct globalmem_dev *globalmem_devp;

struct globalmem_dev dev;    /*设备结构体实例*/

/*文件打开函数*/
int globalmem_open (struct inode *inode, struct file *filp)
{
    /*将设备结构体指针赋值给文件私有数据指针*/
    filp->private_data = globalmem_devp;
    return 0;
}

/*文件释放函数*/
int globalmem_release (struct inode *inode, struct file *filp)
{   
    return 0;
}


/*读函数*/
static ssize_t globalmem_read (struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    printk (KERN_INFO "count is: %d and *ppos is: %ld\n", count, *ppos);
    int ret = 0;
    struct globalmem_dev *dev = filp->private_data;        /*获得设备结构指针*/

    /*分析和获取有效的读长度*/
    if (p >= GLOBALMEM_SIZE) {    /*要读的偏移位置越界*/
        printk (KERN_INFO "p is %ld\n", p);
        return count ? -ENXIO : 0;
    }

    if (count > GLOBALMEM_SIZE - p)    {    /*要读的字节数太大*/
        count = GLOBALMEM_SIZE - p;
        printk (KERN_INFO "COUNT IS %d\n", count);
    }

    /*内核空间->用户空间 */
    printk (KERN_INFO "right to copy_to_user()?\n");
    if (copy_to_user (buf, (void *)(dev->mem + p), count)) {
       
        ret = -EFAULT;
    } else {
            *ppos += count;
            ret = count;
   
            printk (KERN_INFO "read %d bytes(s) from %ld\n", count, p);
    }

    return ret;
}

/*写函数*/
static ssize_t globalmem_write (struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
   
    struct globalmem_dev *dev = filp->private_data;        /*获得设备结构指针*/

    /*分析和获取有效的写长度*/
    if (p >= GLOBALMEM_SIZE)    /*要写的偏移位置越界*/
        return count ? -ENXIO : 0;
   
    if (count > GLOBALMEM_SIZE - p)    /*要写的字节数太多*/
        count = GLOBALMEM_SIZE - p;    /*把剩余的空间都写完*/

    /*用户空间->内核空间*/
    if (copy_from_user (dev->mem + p, buf, count))
        ret = -EFAULT;
    else {
        *ppos += count;
        ret = count;

        printk (KERN_INFO "written %d bytes(s) from %ld\n", count, p);
    }
   
    return ret;
}

/* seek() 函数*/
static loff_t globalmem_llseek (struct file *filp, loff_t offset, int orig)
{
    loff_t ret;
    switch (orig) {
        case 0:
           if (offset < 0) {
            ret = -EINVAL;
            break;
            }

           if ((unsigned int)offset > GLOBALMEM_SIZE) {    /*偏移越界*/
            ret = -EINVAL;
            break;
           }
           filp->f_pos = (unsigned int)offset;        /*从文件头偏移*/
           ret = filp->f_pos;
           break;
            
        case 1:
           if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {    /*偏移越界*/
            ret = -EINVAL;
            break;
           }
           if ((filp->f_pos + offset) < 0) {
            ret = -EINVAL;
            break;
           }
           filp->f_pos += offset;
           ret = filp->f_pos;
           break;

        default:
           ret = -EINVAL;
    }

    return ret;
}

/*ioctl() 设备控制函数*/
static int globalmem_ioctl (struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
{
   
    struct globalmem_dev *dev = filp->private_data;        /*获得设备结构指针*/

    switch (cmd) {
        case MEM_CLEAR:
            /*清全局内存*/
            memset (dev->mem, 0, GLOBALMEM_SIZE);
            printk (KERN_INFO "globalmem is set to zero\n");
            break;

        default:
            return -EINVAL;    /*其他不支持的命令*/
    }

    return 0;
}
   
/*文件操作结构体*/
static const struct file_operations globalmem_fops = {
        .owner = THIS_MODULE,
        .llseek = globalmem_llseek,
        .read = globalmem_read,
        .write = globalmem_write,
        .ioctl = globalmem_ioctl,
    .open = globalmem_open,
    .release = globalmem_release,
};

/*初始化并添加 cdev 结构体*/
static void globalmem_setup_cdev (struct globalmem_dev *dev, int index)
{
        int err;
        int devno = MKDEV (globalmem_major, 0);

        cdev_init (&dev->cdev, &globalmem_fops); /*初始化cdev结构体*/
        dev->cdev.owner = THIS_MODULE;
        dev->cdev.ops = &globalmem_fops;

        err = cdev_add (&dev->cdev, devno, 1);
        if (err) {
                printk (KERN_NOTICE "Error %d adding globalmem", err);
        }
}

/*globalmem 设备驱动模块加载函数*/
int globalmem_init (void)
{
        int result;
        dev_t devno = MKDEV (globalmem_major, 0);

        /*申请字符设备驱动区域*/
        if (globalmem_major) {
                result = register_chrdev_region (devno, 1, "globalmem");
        } else {        /*预设设备号已被占用,动态申请一个*/
                result = alloc_chrdev_region (&devno, 0, 1, "globalmem");
                globalmem_major = MAJOR (devno);        /*分离出主设备号*/
        }

        if (result < 0)
                return result;

    globalmem_devp = kmalloc (sizeof (struct globalmem_dev), GFP_KERNEL);
   
    if (!globalmem_devp) {     /*申请失败*/
        result = -ENOMEM;
        goto fail_malloc;
    }

    memset (globalmem_devp, 0, sizeof (struct globalmem_dev));

    globalmem_setup_cdev (globalmem_devp, 0);
        return 0;

fail_malloc:
    unregister_chrdev_region (devno, 1);
    return result;
}

/*globalmem 设备驱动模块卸载函数*/
void globalmem_exit (void)
{
        cdev_del (&dev.cdev);   /*删除 cdev 结构*/
    kfree (globalmem_devp);
        unregister_chrdev_region (MKDEV (globalmem_major, 0), 1); /*注销设备区域*/
}

MODULE_AUTHOR ("Song Baohua");
MODULE_LICENSE ("Dula BSD/GPL");

module_param (globalmem_major, int, S_IRUGO);

module_init (globalmem_init);
module_exit (globalmem_exit);

编译成功后,使用 insmod 命令把模块加载到内核中,通过 lsmod 命令可以看到模块已经被成功加载:
beyes@linux-beyes:~/Drivers/my/globalmem> lsmod
Module                  Size  Used by
globalmem               7308  0
... ...

再通过 cat 命令查看 /proc/devices 文件,发现多出了主设备号为 240 的字符设备:
beyes@linux-beyes:~/Drivers/my/globalmem> cat /proc/devices
Character devices:
...
240 globalmem
...

现在通过 mknod 命令创建设备节点:
beyes@linux-beyes:~/Drivers/my/globalmem> sudo mknod /dev/globalmem c 240 0
beyes@linux-beyes:~/Drivers/my/globalmem> ll /dev/globalmem
crw-r--r-- 1 root root 240, 0 09-01 12:37 /dev/globalmem

验证一下设备的工作,现在向设备写入一个 hello world :
linux-beyes:/home/beyes/Drivers/my/globalmem # echo 'hello world' > /dev/globalmem
linux-beyes:/home/beyes/Drivers/my/globalmem # cat /dev/globalmem
hello world
cat: /dev/globalmem: 没有那个设备或地址
上面为什么会出现没有设备或地址? 修改了原来程序中的 globalmem_read() 函数,主要是增加了几行测试用的 printk() 函数。现在用 dmesg 看一下内核输出信息:
written 12 bytes(s) from 0
count is: 4096 and *ppos is: 0
right to copy_to_user()?
read 4096 bytes(s) from 0
count is: 4096 and *ppos is: 4096
p is 4096
从内核输出信息可以推断,cat 命令读取设备内存时,是一次以一个页来读取的,即 4096 个字节,如果没遇到 EOF 或结束信息,则一直会以每页为单位的读取下去。分析输出信息的每一行:
第 1 行,向设备写 12 个字节,即 hello world 外加 1 个换行符;
第 2 行,从这 1 行输出可以看出,传到 globalmem_read() 函数的 size 就是 4096 个字节,也就是说,cat 一次用一个内存页来读取设备;
第 3 行,这一行测试可以忽略;
第 4 行,输出读取了多少字节;
第 5 行,注意,到了这里,实际上函数再一次被调用了,这是 cat 命令操作造成的结果。在这里,也可以看到,文件指针已经偏移到了 4096 这里;
第 6 行,由于指针已经越界,所以返回 ENXIO 错误号,这个错误号正是表示没有找到相关设备!这也就是 cat 在输出 hello world 后又输出无法找到设备的提示了。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-9-1 19:18:14 | 只看该作者

ioctl() 函数

上面驱动程序中,ioctl() 函数接受 MEM_CLEAR 命令,这个命令会将全局内存的有效数据长度清零,对于设备不支持的命令,ioctl() 函数应该返回 -EINVAL 。程序中,MEM_CLEAR 被宏定义为 0x01,实际上,这不是一种值得推荐的做法,简单地对命令定义为 0x0, 0x01, 0x02 等类似值会导致不同的设备驱动拥有相同的命令号。如果设备 A、B 都支持 0x0、0x1、0x2 这样的命令,假设用户本身希望给 A 发 0x01 命令,可是不经意间发给了 B,这个时候 B 因为支持该命令,它就会执行该命令。因此,Linux 内核推荐采用一套统一的 ioctl() 命令生成方式。

ioctl() 命令
Linux 系统建议以下图所示的方式定义 ioctl() 的命令码
设备类型
序列号
方向
数据尺寸
8bit
8bit
2bit
13/14bit

命令码的设备类型字段为一个“幻数”,可以是 0~0xff 之间的值,内核中的 ioctl-number.txt 给出了一些推荐的和已经被使用的“幻数”,新设备驱动定义 “幻数“的时候要避免与其冲突。

命令码的序列号字段也是 8 位宽。

命令码的方向字段为 2 位,该字段表示数据传送的方向,可能的值是:
      _IOC_NONE (无数据传输)
      _IOC_READ  (读)
      _IOC_WRITE (写)
      _IOC_READ|_IOC_WRITE (双向)
数据的传送方向是从应用程序的角度来看的。

命令码的数据长度字段表示设计的用户数据的大小,这个成员的宽度依赖于体系结构,通常是 13 位或者是 14 位。

内核还定义了 _IO() 、_IOR() 、_IOW 、IOWR() 这 4 个宏来辅助生成命令。这 4 个宏可以在 include/asm-generic/ioctl.h 文件中找到。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2025-5-4 04:16 , Processed in 0.072227 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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