Chinaunix首页 | 论坛 | 博客
  • 博客访问: 81595
  • 博文数量: 15
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 263
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-16 20:06
个人简介

一执着书生,只求乱世太平

文章分类

全部博文(15)

文章存档

2013年(15)

我的朋友

分类: 嵌入式

2013-05-13 23:10:56

Refrences:

[1] LDD3
[2] Linux操作系统原理与应用,陈莉君

1                   高级字符设备驱动程序

从此处开始,如不作特别说明,使用的代码都是基于2.6.38版本内核。

1.1           内核等待队列与阻塞

在设计简单字符设备驱动程序时跳过的一个问题:当设备无法立刻满足用户读写请求时该如何处理?比如应用程序调用read而无数据可读或数据不足,调用write而设备暂时无法接受数据或没有足够的空间写入数据,应用程序不会关心这种问题。这都需要通过驱动程序缺省的实现阻塞,使进程睡眠,直到请求可以满足。

阻塞是文件读写默认(缺省)方式。应用程序员在打开文件时设置O_NONBLOCK#include )标志实现非阻塞,这种情况下,当请求无法立刻满足,驱动程序立即返回-EAGAIN

Linux设备驱动程序中使用等待队列实现阻塞,等待队列中的进程都会进入睡眠状态。等待队列操作包括3步:定义及初始化、阻塞(进程添加到等待队列)、唤醒。定义及初始化等待队列

wait_queue_head_t mycdev_inq, mycdev_outq;  /* 定义 */

init_waitqueue_head(&mycdev_inq);             /* 初始化 */

init_waitqueue_head(&mycdev_outq);

/* 定义并初始化的令一种方法

 * DECLARE_WAIT_QUEUE_HEAD(mycdev_inq);

 */

等待队列3种阻塞方式

// 一下3种阻塞方式都满足当condition=true立即返回

/* 阻塞时让进程进入TASK_UNINTERRUPTIBLE模式睡眠,并挂在mycdev_inq参数指定队列上 */

wait_event(mycdev_inq, condition);

/* 阻塞时让进程进入TASK_INTERRUPTIBLE模式睡眠,并挂在mycdev_inq参数指定队列上 */

wait_event_interruptible(mycdev_inq, condition);

/* 阻塞时让进程进入TASK_KILLABLE模式睡眠,并挂在mycdev_inq参数指定队列上 */

int wait_event_killable(mycdev_inq, condition);

进程唤醒方法

/* q中唤醒状态为TASK_UNINTERRUPTIBLETASK_INTERRUPTIBLETASK_KILLABLE的所有进程*/

wake_up(wait_queue_t *q);

/* q中唤醒状态为TASK_INTERRUPTIBLE的所有进程*/

wake_up_interruptible(wait_queue_t *q);

1.2           高级字符设备操作

static const struct file_operations mycdev_fops = {

    .unlocked_ioctl = mycdev_ioctl,  /* IO控制*/

    .poll = mycdev_poll,

    .mmap = mycdev_mmap,             /* 映射*/

};

每个操作的内核接口为

unsigned int (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

1.2.1              关于unlocked_ioctl

long (*unlocked_ioctl) (struct file *flip, unsigned int cmd, unsigned long arg);

flip:打开的设备文件

cmd:执行的命令

arg:用户空间传进来的参数,定义为ulong类型,也可以将指针传入

unlocked_ioctl方法实现步骤:

1)定义命令cmd,需要包含#include

unlocked_ioctl的命令分为4个位段:类型(幻数,8bits)、序号(8bits)、传送方向、参数大小(13~14bits)。Documents/ioctl/ioctl-number.txt中罗列了对系统已使用的幻数,幻数指定了设备类型。序号表明设备命令中的第几号命令(一般从0开始,但不一定)。传送方向可以为_IOC_NONE(无数据传送)、_IOC_READ(从设备读)、_IOC_WRITE(写入设备),注意此处的传送方向都是指从应用程序的角度看的。参数大小指用户传送数据大小。

命令的构造通过内核提供的宏定义来定义:

_IO(type, nr)                 // 无参数命令

_IOR(type, nr, datatype)    // 从驱动中读数据

_IOW(type, nr, datatype)    // 写数据到驱动

_IOWR(type, nr, datatype)   // 双向传送

datatype取值可以为一般的数据类型(int,long等),也可以是预定义的typedef类型(struct,enum等)。如下为mycdev模块中定义的命令

#define MYCDEV_IOC_MAGIC           'k'

 

#define MYCDEV_IOC_PRINT           _IO(MYCDEV_IOC_MAGIC, 0)

#define MYCDEV_IOC_GETDATA         _IOR(MYCDEV_IOC_MAGIC, 1, int)

#define MYCDEV_IOC_SETDATA         _IOW(MYCDEV_IOC_MAGIC, 2, int)

 

#define MYCDEV_IOC_MAXNR           (3)

2)使用switch…case实现命令

当传入的命令为无效命令(不匹配任何已有的命令)时,unlocked_ioctl返回-EINVAL(“非法参数”)。

如果传入的arg只是整数,则直接可用;若arg传入的为指针,使用前必需对其进行检查,保证指针指向的用户地址有效,检查的方法如下

int access_ok(int type, const void *addr, unsigned long size);

type:VERIFY_READVERIFY_WRITE

addr:指要操作的用户内存地址,为unlocked_ioctl传入的arg

size:操作数据的长度

成功返回1,否则返回0,返回0unlocked_ioctl返回-EFAULT.

检查参数有效性之后就是使用switch…case实现命令了,具体实现参考本节中的代码。

在需要从用户空间获取小量数据时,可以使用__put_user__get_user分别代替copy_to_usercopy_from_user,此时必须使用access_ok检查用户地址有效性。使用put_userget_user可避免使用access_ok

1.2.2              关于mmap

在分析file_operations中的mmap之前,先了解:系统调用的mmap负责将文件内容映射到进程的虚拟地址空间,然后可以直接对映射的虚拟内存空间执行读写操作而不必使用诸如readwrite等去访问文件。mmapmunmap的系统调用如下

void *mmap(void *start, int length, int prot, int flags, int fd, int offset);

start:映射内存的起始地址,通常设为NULL由系统指定

length:需要映射到内存的文件长度

prot:映射区保护方式,有PROT_EXEC,PROT_READ,PROT_WRITE

flags:映射区特性,有MAP_SHARED(修改会写回文件,多个进程能映射同一地址实现共享),MAP_PRIVATE(修改不写回文件,写入操作会产生映射区的一个复制)

fd:文件描述符

offset:开始映射的文件偏移量

返回值:虚存区起始地址

 

int munmap(void *start, size_t length);

mmap的一个系统调用实例,

#include

#include

#include

#include

#include

#include

#include

#include

 

#define    FILE_MODE    (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

#define msg         "abc"

 

int main(int argc, char *argv[])

{

    int        fdin, fdout;

    void       *src, *dst;

    struct  stat statbuf;

 

    if (argc != 3)

    {

        printf("usage: %s \n", argv[0]);

        return 0;

    }

    if ((fdin = open(argv[1], O_RDONLY)) < 0)

    {

        printf("err: can't open %s for reading/n", argv[1]);

        return 0;

    }

    if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)

    {

        printf("err: can't create %s for writing/n", argv[2]);

        return 0;

    }

    if (fstat(fdin, &statbuf) < 0)

    {

        printf("fstat error/n");

        return 0;

    }

    /*  set size of output file */

    if (lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1)

    {

        printf("lseek error/n");

        return 0;

    }

    if (write(fdout, "", 1) != 1)

    {

        printf("write error/n");

        return 0;

    }

    if ((src = mmap(0, statbuf.st_size, PROT_READ , MAP_SHARED, fdin, 0)) == MAP_FAILED)

    {

        printf("mmap error for input/n");

        return 0;

    }

    if ((dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE , MAP_SHARED , fdout, 0)) == MAP_FAILED)

    {

        printf("mmap error for output/n");

        return 0;

    }

    memcpy(dst, src, statbuf.st_size);

    exit(0);

}

将设备映射指把用户空间的一段地址关联到设备内存上,当程序读写用户空间地址时实际上是在访问设备。file_operations中的mmap功能:建立虚拟地址到物理地址的页表。

int (*mmap) (struct file *, struct vm_area_struct *);

struct vm_area_struct是虚存区结构,是虚拟地址的数据结构描述.

mmap中调用remap_pfn_range可一次建立所有的页表,完整实例参考“高级字符设备驱动实例”一节。

#include

/**

 * remap_pfn_range - remap kernel memory to userspace

 * @vma: user vma to map to

 * @addr: target user address to start at

 * @pfn: physical address of kernel memory

 * @size: size of map area

 * @prot: page protection flags for this mapping

 *

 *  Note: this is only safe if the mm semaphore is held when called.

 */

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,

           unsigned long pfn, unsigned long size, pgprot_t prot)

1.2.3              高级字符设备驱动实例

本实例在“简单字符设备驱动”一节的基础上改进,添加了unlocked_ioctl的示例实现、read的阻塞操作和mmap的实现,将静态创建设备文件改为动态创建设备文件,实验中将MYCDEV_MAJOR设为0动态分配主设备号。

/*

 * ===================================================================================

 *       Filename:  mycdev.c

 *

 *    Description: 

 *

 *        Version:  1.0 (02/26/2013)

 *        Created:  xhzuoxin(xiahouzuoxin@163.com)

 *       Compiler:  gcc

 * ===================================================================================

 */

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

 

#include "mycdev.h"

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("xhzuoxin");

 

/*

 * if 0 : dinamic alloc device number

 * else : static device number

 */

#define MYCDEV_MAJOR               (0)

 

 

struct mycdev {

    struct cdev cdev;

    unsigned char mem[MYCDEV_SIZE];  /* 使用内存区模拟字符设备 */

    wait_queue_head_t inq, outq;           /* 阻塞用等待队列 */

};

 

/* global variables */

static dev_t mycdev_major = MYCDEV_MAJOR;   /* 主设备号 */

dev_t devno = 0;                            /* 设备号 */

struct mycdev *devp;                        /* 设备结构 */

struct class *myclass;                      /* 动态创建设备文件需要的class */

bool read_able = false;                     /* 是否有数据可读标志,初始化为flase */

 

 

static int mycdev_open(struct inode *inode, struct file *fp)

{

    fp->private_data = devp;  /* 通过private_data避免全局变量的使用 */

 

    return 0;

}

 

static int mycdev_release(struct inode *inode, struct file *fp)

{

    return 0;

}

 

static ssize_t mycdev_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)

{

    unsigned long p = *pos;

    unsigned int count = size;

    struct mycdev *tmp_dev = fp->private_data;

 

    /* 阻塞处理 */

    while (!read_able) {  /* 使用while,唤醒进程时也应该检测该标志 */

       if (fp->f_flags & O_NONBLOCK) {

           return -EAGAIN;

       }

       wait_event_interruptible(tmp_dev->inq, read_able); /* 等待处理 */

    }

 

    /* 读过程 */

    if (p >= MYCDEV_SIZE) {

       read_able = false;

       return -1;

    }

    if (count > MYCDEV_SIZE - p) {

       count = MYCDEV_SIZE - p;

       read_able = false;

    }

    if (copy_to_user(buf, (void *)(tmp_dev->mem + p), count) != 0) {

       printk("read error!\n");

       return -1;

    } else {

       *pos += count;  /* 修改偏移量 */

       printk(KERN_INFO"read %d bytes from %ld\n", count, p);

    }

 

    return count;

}

 

static ssize_t mycdev_write(struct file *fp, const char __user *buf, size_t size, loff_t *pos)

{

    unsigned long p = *pos;

    unsigned int count = size;

    struct mycdev *tmp_dev = fp->private_data;

 

    if (p > MYCDEV_SIZE) {

       return -1;

    }

    if (p > MYCDEV_SIZE - count) {

       count = MYCDEV_SIZE - p;

    }

    if (copy_from_user((void *)(tmp_dev->mem + p), buf, count) != 0) {

       return -1;

    } else {

       *pos += count;

       printk(KERN_INFO"write %d bytes from %ld\n", count, p);

    }

 

    read_able = true;

    /* 唤醒阻塞进程 */

    wake_up_interruptible(&(tmp_dev->inq));

 

    return count;

}

 

static loff_t mycdev_llseek(struct file *fp, loff_t off, int whence)

{

//  struct mycdev *dev = fp->private_data;

    loff_t new_pos = 0;

 

    switch(whence) {

    case SEEK_SET:

       new_pos = off;

       break;

    case SEEK_CUR:

       new_pos = fp->f_pos + off;

       break;

    case SEEK_END:

       new_pos = MYCDEV_SIZE + off;

    }

 

    if (new_pos < 0) {

       return -EINVAL;

    } else {

       fp->f_pos = new_pos;

       return new_pos;

    }

}

 

static long mycdev_unlocked_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)

{

    long retval = 0;

    int err = 0;

    int ioarg = 0;

 

    /* 检测命令是否有效 */

    if (_IOC_TYPE(cmd) != MYCDEV_IOC_MAGIC) {

       return -EINVAL;

    } else if (_IOC_NR(cmd) > MYCDEV_IOC_MAXNR) {

       return -EINVAL;

    }

 

    /* 根据命令检测参数空间是否可访问 */

    if (_IOC_DIR(cmd) & _IOC_READ)

       err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));

    else if (_IOC_DIR(cmd) & _IOC_WRITE)

       err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));

    if (err) return -EFAULT;

 

    /* 执行命令 */

    switch(cmd) {

    case MYCDEV_IOC_PRINT:

       printk("====== CMD:MYCDEV_IOC_PRINT Done ======\n");

       break;

 

    case MYCDEV_IOC_GETDATA:

       ioarg = 4321;

       retval = __put_user(ioarg, (int *)arg);

       break;

 

    case MYCDEV_IOC_SETDATA:

       retval = __get_user(ioarg, (int *)arg);

       printk("====== GETDATA From UserSpace:%d ======\n", ioarg);

       break;

 

    default:

       return -ENOTTY;

    }

 

    return retval;

}

 

int mycdev_mmap(struct file *flip, struct vm_area_struct *vma)

{

    struct mycdev *dev = flip->private_data;

 

    vma->vm_flags |= VM_IO;

    vma->vm_flags |= VM_RESERVED;

 

    if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(dev->mem)<

       vma->vm_end-vma->vm_start, vma->vm_page_prot)) {

       return -EAGAIN;

    }

 

    return 0;

}

 

/* paddingg struct file operation */

static const struct file_operations mycdev_fops = {

    .owner = THIS_MODULE,

    .read = mycdev_read,

    .write = mycdev_write,

    .open = mycdev_open,

    .release = mycdev_release,

    .llseek = mycdev_llseek,

    .unlocked_ioctl = mycdev_unlocked_ioctl,

    .mmap = mycdev_mmap,

};

 

static void setup_mycdev(struct mycdev *dev, int index)

{

    int ret;

    int devno = MKDEV(mycdev_major, index);

 

    cdev_init(&dev->cdev, &mycdev_fops);

    dev->cdev.owner = THIS_MODULE;

    dev->cdev.ops = &mycdev_fops;

    ret = cdev_add(&dev->cdev, devno, 1);

 

    if (ret) {

       printk("adding mycdev error!\n");

    }

}

 

static int __init mycdev_init(void)

{

    int ret;

   

    if (mycdev_major) { /* 静态分配 */

       devno = MKDEV(mycdev_major, 0);

       ret = register_chrdev_region(devno, 1, "mycdev");

    } else {  /* 动态分配 */

       ret = alloc_chrdev_region(&devno, 0, 1, "mycdev");

       mycdev_major = MAJOR(devno);

    }

 

    /* 动态创建设备文件 */

    myclass = class_create(THIS_MODULE, "mycdev_driver");

    device_create(myclass, NULL, devno, NULL, MYCDEV_NAME);

 

    /* 分配设备空间,等同于cdev_alloc */

    devp = kmalloc(sizeof(struct mycdev), GFP_KERNEL);

    if (!devp) {

       ret = -ENOMEM;

       unregister_chrdev_region(devno, 1);

       return ret;

    }

    memset(devp, 0, sizeof(struct mycdev));

 

    /* 初始化等待队列 */

    init_waitqueue_head(&devp->inq);

    init_waitqueue_head(&devp->outq);

 

    /* 添加并启动设备 */

    setup_mycdev(devp, 0);

 

    return 0;

}

 

static void __exit mycdev_exit(void)

{

    printk("mycdev module is leaving...\n");

    cdev_del(&devp->cdev);

    kfree(devp);

    unregister_chrdev_region(MKDEV(mycdev_major, 0), 1);

    /* 删除设备文件 */

    device_destroy(myclass, devno);

    class_destroy(myclass);

}

 

module_init(mycdev_init);

module_exit(mycdev_exit);

Makefile与“简单字符设备驱动”一节相同,测试文件

/*

 * ===================================================================================

 *       Filename:  usr_test.c

 *

 *    Description: 

 *

 *        Version:  1.0 (02/26/2013)

 *        Created:  xhzuoxin(xiahouzuoxin@163.com)

 *       Compiler:  gcc

 * ===================================================================================

 */

#include

#include

#include

#include

#include

#include

#include "mycdev.h"

 

int main(void)

{

    int testdev;

    int n, i, ret;

    char write_buf[] = "xiahouzuoxin";

    char buf[12];

 

    sprintf(buf, "/dev/%s", MYCDEV_NAME);

    testdev = open(buf, O_RDWR);

    if (testdev == -1) {

       printf("cannot open file.\n");

       exit(1);

    }

    memset(buf, 0, sizeof(buf)/sizeof(char));

 

    n = sizeof(write_buf)/sizeof(char);

    if (ret = write(testdev, write_buf, n) < n) {

       printf("write error!\n");

       exit(1);

    }

 

    lseek(testdev, 0, SEEK_SET);

    if (ret = read(testdev, buf, n) < n) {

       printf("read error!\n");

       exit(1);

    }

    for (i=0; i

       printf("%c", buf[i]);

    }

    printf("\n");

    /* test @ioctl */

    {

       int ioarg = 9000;

 

       if (ioctl(testdev, MYCDEV_IOC_PRINT) < 0) {

           printf("error in ioctl: MYCDEV_IOC_PRINT.\n");

           return -1;

       }

       if (ioctl(testdev, MYCDEV_IOC_SETDATA, &ioarg) < 0) {

           printf("error in ioctl: MYCDEV_IOC_SETDATA.\n");

           return -1;

       }

       if (ioctl(testdev, MYCDEV_IOC_GETDATA, &ioarg) < 0) {

           printf("error in ioctl: MYCDEV_IOC_GETDATA.\n");

           return -1;

       } else {

           printf("GETDATA:%d\n", ioarg);

       }

        

    }

    close(testdev);

 

    return 0;

}

./usr_test可观察用户空间执行结果,使用tail –n 4 /var/log/message观察驱动程序打印信息

[root@CentOS mycdev]# ./usr_test

xiahouzuoxin

GETDATA:4321

[root@CentOS mycdev]# tail -n 4 /var/log/messages

May 12 13:03:23 CentOS kernel: write 13 bytes from 0

May 12 13:03:23 CentOS kernel: read 13 bytes from 0

May 12 13:03:23 CentOS kernel: ====== CMD:MYCDEV_IOC_PRINT Down ======

May 12 13:03:23 CentOS kernel: ====== GETDATA From UserSpace:9000 ======

[root@CentOS mycdev]#

阅读(1472) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~