文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>ldd3学习笔记---字符设备

ldd3学习笔记---字符设备

时间:2009-04-16  来源:gaozhenbo1985

一晃,这一个月又荒荒过去了,什么也没干,状态低糜。今天整理下状态,争取把字符设备驱动程序这一章的学习笔记整理完。   一,主设备号和次设备号   主设备号用于标识设备对应的驱动程序。次设备号用于正确确定设备文件所指的设备,由内核使用。   1,设备编号的内部表示:   内核中,用dev_t类型(在<linux/types.h>中定义)来保存设备编号---包括主设备号和次设备号。 主设备号,次设备号和dev_t之间的转换。 dev_t --------> 主设备号 + 次设备号: MAJOR(dev_t dev);  获得主设备号 MINOR(dev_t dev);  获得次设备号 主设备号 + 次设备号 ---------> dev_t MKDEV(int major, int minor);    2,分配和释放设备编号   在建立一个字符设备之前,首先要做的就是获取一个或者多个设备编号。 静态分配设备号: int register_chrdev_region(dev_t first, unsigned int count, char *name); 动态分配设备号: int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 释放设备号: void unregister_chrdev_region(dev_t first, unsigned int count);   通常,可在模块的加载函数中分配设备号,在模块的注销函数中释放设备号,scull的相应代码如下:  

if (scull_major) {
        dev = MKDEV(scull_major, scull_minor);
        result = register_chrdev_region(dev, scull_nr_devs, "scull");
    } else {
        result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
        scull_major = MAJOR(dev);
    }

可见,当用户指定了主设备号和次设备号时,用的是静态分配的方式。当没指定的时候用的是动态的方式。

注意:分配主设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。

二,重要的数据结构

大部分字符设备驱动程序操作都会涉及到三个重要的内核数据结构。file_operations,file,inode.

文件操作: 结构file_operations:把自定义的操作与cdev的设备操作联系起来。  

struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .read = scull_read,
    .write = scull_write,
    .open = scull_open,
    .release = scull_release,
};

然后会在字符设备初始化的函数中,将这些操作与cdev设备对应起来。

static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    int err, devno = MKDEV(scull_major, scull_minor + index);
    
    cdev_init(&dev->cdev, &scull_fops);
    dev->cdev.owner = THIS_MODULE;
//    dev->cdev.ops = &scull_fops;

    err = cdev_add (&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

file结构:表示一个打开的文件。由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数。

inode结构:内核用此结构表示磁盘上存储的文件。

其中对驱动程序代码有用的为:

dev_t i_rdev:表示设备编号。

struct cdev *i_cdev: struct cdev是表示字符设备的内核数据结构。当inode指向一个字符设备文件时,该字段包含了对应的struct cdev结构的指针。

考虑到可移至性,我们不应该直接对i_rdev进行操作,而是使用以下两个宏来从inode中获得主设备号和此设备号。

unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

三,字符设备的注册

内核内部使用struct cdev结构来表示字符设备。通常是将cdev结构嵌入到自己的特定设备结构中。在内核调用设备的操作之前,必须分配并注册该结构。代码中应该包含<linux/cdev.h>

注册部分有两种方法:2.6的方法和以前的方法。

2.6的方式:用cdev接口。

1,分配内存:

struct cdev *my_cdev = cdev_alloc();

2,初始化:

void cdev_init(struct cdev *cdev, struct file_operations, *fops);

这里,将操作集与cdev联系了起来。

3,注册:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

这里要注意的是,跟网络驱动程序一样,一旦该结构注册到内核中,则内核就可以调用其驱动程序操作该设备了,所以一定要都准备完成之后才可以把设备注册内核中。

4,注销:

void cdev_del(struct cdev *dev);

2.6以前的方法:

注册:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

这里,注册了以后内核会为该设备建立一个默认的cdev结构。

注销:

int unregister_chrdev(unsigned int major, const char *name);

应该使用新方法。snull对应的初始化注册代码为:

/*
 * Set up the char_dev structure for this device.
 */
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    int err, devno = MKDEV(scull_major, scull_minor + index);
    
    cdev_init(&dev->cdev, &scull_fops);
    dev->cdev.owner = THIS_MODULE;
//    dev->cdev.ops = &scull_fops;

    err = cdev_add (&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

到目前位置,我们已经我们的字符设备注册到内核中了,下面来看看其中的一些设备方法。

四,open和release

open方法提供给驱动程序以初始化的能力,从而为以后的操作完成初始化做准备。在大多数驱动程序中,open应该完成以下工作:

1)检查设备是否有特定的错误(诸如设备未就绪或类似的硬件问题);

2)如果设备是首次打开,则对其进行初始化。

3)如有必要,更新f_op指针。

4)分配并填写置于filp->private_data里的数据结构。

下面是经过简化的scull的open代码:

int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev; /* device information */

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev; /* for other methods */

    /* now trim to 0 the length of the device if open was write-only */
    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        scull_trim(dev); /* ignore errors */
        up(&dev->sem);
    }
    return 0; /* success */
}

这里有个container_of()函数。此函数的作用是通过cdev结构找到包含cdev结构的scull_dev结构。

一旦找到scull_dev以后,scull将一个指针保存到了file结构的private_data中,以便以后对该指针进行访问。(为什么不直接用一个scull_dev的指针来保存呢??这里我认为是因为file代表的是一个打开的文件,所以应该更合理一些)

release方法:

release方法的作用正好与open相反。一般应该完成以下工作:

1)释放由open分配的,保存在filp->private_data中的所有内容。

2)在最后一次关闭操作时关闭设备。

  五,read和write   read和write方法完成的任务类似,即拷贝数据到应用程序空间,或反过来从应用程序空间拷贝数据。函数原型为: ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp); ssize_t write(struct file *filp, char __user *buff, size_t count, loff_t *offp);   这里要注意:read和write方法的buff参数是用户空间的指针。因此,内核代码不能直接引用其中的内容。所以我们要通过特定的函数来对这个用户空间的缓冲区进行操作。如下:   unsigned long copy_to_user(void __user *to, const void *from, unsigned long count); unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);   这两个函数在<asm/uaccess.h>中定义。   这两个函数也是read和write的核心函数。   snull对应的read代码如下:

ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
    struct scull_dev *dev = filp->private_data;
    struct scull_qset *dptr;    /* the first listitem */
    int quantum = dev->quantum, qset = dev->qset;
    int itemsize = quantum * qset; /* how many bytes in the listitem */
    int item, s_pos, q_pos, rest;
    ssize_t retval = 0;

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    if (*f_pos >= dev->size)
        goto out;
    if (*f_pos + count > dev->size)
        count = dev->size - *f_pos;

    /* find listitem, qset index, and offset in the quantum */
    item = (long)*f_pos / itemsize;
    rest = (long)*f_pos % itemsize;
    s_pos = rest / quantum; q_pos = rest % quantum;

    /* follow the list up to the right position (defined elsewhere) */
    dptr = scull_follow(dev, item);

    if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
        goto out; /* don't fill holes */

    /* read only up to the end of this quantum */
    if (count > quantum - q_pos)
        count = quantum - q_pos;

    if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
        retval = -EFAULT;
        goto out;
    }
    *f_pos += count;
    retval = count;

  out:
    up(&dev->sem);
    return retval;
}

write的代码:

ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
    struct scull_dev *dev = filp->private_data;
    struct scull_qset *dptr;
    int quantum = dev->quantum, qset = dev->qset;
    int itemsize = quantum * qset;
    int item, s_pos, q_pos, rest;
    ssize_t retval = -ENOMEM; /* value used in "goto out" statements */

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;

    /* find listitem, qset index and offset in the quantum */
    item = (long)*f_pos / itemsize;
    rest = (long)*f_pos % itemsize;
    s_pos = rest / quantum; q_pos = rest % quantum;

    /* follow the list up to the right position */
    dptr = scull_follow(dev, item);
    if (dptr == NULL)
        goto out;
    if (!dptr->data) {
        dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
        if (!dptr->data)
            goto out;
        memset(dptr->data, 0, qset * sizeof(char *));
    }
    if (!dptr->data[s_pos]) {
        dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
        if (!dptr->data[s_pos])
            goto out;
    }
    /* write only up to the end of this quantum */
    if (count > quantum - q_pos)
        count = quantum - q_pos;

    if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
        retval = -EFAULT;
        goto out;
    }
    *f_pos += count;
    retval = count;

        /* update the size */
    if (dev->size < *f_pos)
        dev->size = *f_pos;

  out:
    up(&dev->sem);
    return retval;
}

可以看出,这两个函数的大概流程就是找到位置,然后调用copy_to_user()或者copy_from_user(),然后更新指针位置。

ps:虽然感觉字符设备的大体流程和框架比较简单,但还是拖拖拉拉的弄了挺长时间,应该注意下自己的效率了。。

相关阅读 更多 +
排行榜 更多 +
平衡球球

平衡球球

休闲益智 下载
平衡球球游戏

平衡球球游戏

休闲益智 下载
土耳其方块消除游戏

土耳其方块消除游戏

休闲益智 下载