linux字符设备驱动程序怎么写

摘要:linux设备驱动程序第三版第三章笔记

1.scull:simple character utility for loading localities.

2.scull0-scull3: 4个global and persistent设备,映射到的物理内存区是不会消失的。

  scullpipe0-scullpipe3:4个FIFO设备,一个进程读一个进程写。

3.主次编号:

  3.1

  major number标志了设备相联系的driver。

  minor number用来决定引用了哪个设备。

  3.2 设备编号内部表示:

  dev_t类型<linux/types.h>中含有设备编号

  获得dev_t的主次编号,使用<linux/kdev_t.h>中的:

    MAJOR(dev_t dev); 
    MINOR(dev_t dev);
 将主次编号转换为dev_t使用:
  MKDEV(int major, int minor); 

  3.3分配和释放设备编号:
  头文件<linux/fs.h>
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);
//动态分配,一旦分配了,可以在/proc/devices中读取
void unregister_chrdev_region(dev_t first, unsigned int count); 


3.4主编号的动态分配:
一旦主编号动态分配了,就可以到/proc/device中来读取主编号,可以使用一个script来代替调用insmod,在insmod以后创建special files。
下面就是脚本scull_load:
#!/bin/sh
module="scull"
device="scull"
mode="664"

# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1

# remove stale nodes
rm -f /dev/${device}[0-3]

major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) 
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3

# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
group="staff"
grep -q '^staff:' /etc/group || group="wheel"

chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3]

 另外还有一个脚本scull_unload来清理/dev目录并去除模块。

分配主编号的示例代码:

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);
}
if (result < 0) {
 printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
 return result;
}

  

4.file_operation:

file_operation结构定义在<linux/fs.h>是一个函数指针集合。每个打开文件用file指针指向,包含一个称为f_op的成员指向file_operation结构。这些函数负责实现系统调用。
当parameter包含__user这样的字符串的时候,这是一种documentation,表示指向的是一个user-space地址。例如:char __user *。

文件操作函数有许多,scull设备驱动仅仅实现相对重要的函数:
struct file_operations scull_fops = {
    .owner =    THIS_MODULE,
    .llseek =   scull_llseek,
    .read =     scull_read,
    .write =    scull_write,
    .ioctl =    scull_ioctl,
    .open =     scull_open,
    .release =  scull_release,
};

 

5.struct file:(代表打开文件描述符) 

struct file 定义在<linux/fs.h>,它与用户空间FILE没有任何关系,它由内核在open时创建,并传递给在文件上操作的任何函数,直至关闭,最后释放这个数据结构。
内核中struct file的指针通常是file 或者filp。
其中重要成员:
mode_t f_mode;
struct file_operations *f_op;
void *private_data;
loff_t f_pos;

struct dentry *f_dentry;

6.inode(表示文件)

inode结构由内核在内部用来表示文件,与file不同,可以有许多struct file指针指向一个inode,但是inode是唯一的。

inode有大量文件信息,但是只有两个成员对于编写驱动有用:

1)dev_t i_rdev; 包含实际设备编号

2)struct cdev * i_cdev; 代表字符设备。

unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

7.字符设备注册
内核中使用struct cdev来代表字符设备,<linux/cdev.h>中包含了这个结构和它相关连的函数。下面的
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
//----实际上SCULL用的是特定的结构,如下-------------------
void cdev_init(struct cdev *cdev, struct file_operations *fops);
//添加字符设备
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
//去除字符设备
void cdev_del(struct cdev *dev);

  scull设备使用一个stuct scull_dev类型的结构来表示每个设备:

1 struct scull_dev { 
2  struct scull_qset *data;          /* Pointer to first quantum set */ 
3  int quantum;                  /* the current quantum size */ 
4  int qset;                      /* the current array size */ 
5  unsigned long size;             /* amount of data stored here */ 
6  unsigned int access_key;         /* used by sculluid and scullpriv */ 
7  struct semaphore sem;          /* mutual exclusion semaphore  */ 
8  struct cdev cdev;               /* Char device structure */
9 };

上述结构初始化过程如下:

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

 8.open和release:

open 方法的原型是:

int (*open)(struct inode *inode, struct file *filp);


1)确定打开的是哪个设备:

  a.通过INODE参数:inode参数成员i_cdev包含cdev,转化成scull_dev类型(如下dev)。


struct scull_dev *dev;                              /* device information */

dev = container_of(inode->i_cdev, struct scull_dev, cdev);

filp->private_data = dev;                            /* for other methods */


  b.查看inode结构的次编号,如果是用register_chrdev注册的设备,就必须用这种方法。

最终,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)
        {
                scull_trim(dev); /* ignore errors */
        }
        return 0; /* success */
}

  2) release 方法:

    目的:释放open分配在filp->private_data中的任何东西,在最后的close关闭设备。


int scull_release(struct inode *inode, struct file *filp)
{
 return 0;
}

  9.read和write:

ssize_t read(struct file *filp, char __user *buff,size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);

  


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

  

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;

}

  



===============================分割线================================================================
一整天就搞了这麽个东西,做笔记真是个累人的事情,要提高效率,感觉自己特别急躁,可能是没有分派好任务的缘故。
作业很多,压力很大,但我却并没有尽全力,反而玩游戏什么的浪费了不少时间。sign!绝对不能这样了。

原文地址:https://www.cnblogs.com/bubbler/p/2500416.html