操作系统实验3

来源:互联网 发布:淘宝子账号 编辑:程序博客网 时间:2024/06/05 01:12

linux 设备驱动程序开发

实验要求:
编写一个字符设备驱动程序,要求实现对该字符设备的打开、读、写、I/O 控制和关闭 5 个基本操作。为了避免牵涉到汇编语言,这个字符设备并非一个真实的字符设备,而是用一段内存空间来模拟的。以模块方式加载该驱动程序。

实验步骤

  1. 字符设备驱动
#include <linux/fs.h> //定义文件表结构(file 结构,buffer_head,m_inode 等)#include <linux/types.h> //对一些特殊的系统数据类型的定义,例如 dev_t, off_t,#include <linux/cdev.h> //包含了 cdev 结构及相关函数的定义。#include <linux/uaccess.h> //包含 copy_to_user(),copy_from_user()的定义#include <linux/module.h> //模块编程相关函数#include <linux/init.h> //模块编程相关函数#include <linux/kernel.h>#include <linux/slab.h>//包含内核的内存分配相关函数,如 kmalloc()/kfree()等MODULE_LICENSE("GPL");#define MEMSIZE 512static const int MAJOR = 255;static const int COUNT = 5;struct mymem_dev{    struct cdev cdev;    unsigned char mem[MEMSIZE];}my_cdev;static int char_dev_open(struct inode *inode, struct file *file) {    file->private_data = (void *)&my_cdev;    printk(KERN_ALERT"open.\n");    return 0;}ssize_t char_dev_read(struct file *file,char __user *buff,size_t COUNT,loff_t *offp) {    unsigned long off = *offp;    struct mymem_dev *dev = file->private_data;    int ret = 0;    if (off >= MEMSIZE) return 0;    if (COUNT + off > MEMSIZE) COUNT = MEMSIZE - off;    if (copy_to_user((void*)buff, (void*)(dev->mem + off), COUNT)){            ret =  -EFAULT;    }    else{            *offp += COUNT;            ret = COUNT;    }    return ret;}ssize_t char_dev_write(struct file *file,const char __user *buff,size_t COUNT,loff_t *offp) {    unsigned long off = *offp;    int ret = 0;    struct mymem_dev *dev = file->private_data;    if (off >= MEMSIZE)            return 0;    if (COUNT + off > MEMSIZE)    /*要写入的字节大于设备的内存空间*/        COUNT = MEMSIZE - off;    if (copy_from_user(dev->mem + off, buff, COUNT))            ret =  -EFAULT;    else {            *offp += COUNT;      /*增加偏移位置*/              ret = COUNT;      /*返回实际的写入字节数*/     }    return ret;}static loff_t char_dev_llseek(struct file *file, loff_t offset, int whence) {     loff_t newpos;          switch(whence) {      case 0: /* SEEK_SET */       /*相对文件开始位置偏移*/         newpos = offset;           /*更新文件指针位置*/        break;      case 1: /* SEEK_CUR */        newpos = file->f_pos + offset;            break;      case 2: /* SEEK_END */        newpos = MEMSIZE - 1 + offset;        break;      default: /* can't happen */        return -EINVAL;    }    if ((newpos < 0) || (newpos > MEMSIZE))        return -EINVAL;    file->f_pos = newpos;    return newpos;}static long char_dev_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) {    printk(KERN_ALERT"ioctl\n");    return 0;}static int char_dev_release (struct inode *node, struct file *file) {    printk(KERN_ALERT"release.\n");    return 0;}   static struct file_operations char_dev_fops = {    .owner = THIS_MODULE,    .open = char_dev_open, // 打开设备    .read = char_dev_read, // 实现设备读功能    .write = char_dev_write, // 实现设备写功能    .unlocked_ioctl = char_dev_ioctl,    .release = char_dev_release,    .llseek = char_dev_llseek,}; // 实现设备控制功能static int __init my_dev_init(void) {    int ret;    ret = register_chrdev_region(MKDEV(MAJOR, 0), COUNT, "my_dev");    if (ret != 0) {        printk(KERN_ALERT"register fail\n");        return 0;    }    cdev_init(&my_cdev.cdev, &char_dev_fops);    my_cdev.cdev.owner = THIS_MODULE;    cdev_add(&my_cdev.cdev, MKDEV(MAJOR, 0), COUNT);    printk(KERN_ALERT"init\n");    return 0;}static void __exit my_dev_exit(void) {    cdev_del(&my_cdev.cdev);    unregister_chrdev_region(MKDEV(MAJOR, 0), COUNT);    printk(KERN_ALERT"exit\n");}module_init(my_dev_init);module_exit(my_dev_exit);

2 测试程序

#include <stdio.h>#include <fcntl.h>  // open函数#include <unistd.h> // write read lseek#include <string.h>int main(){    int fp = 0;    char Buf[4096] = {0};    int ret = 0;    /*打开设备文件*/    fp = open("/dev/my_cdev", O_RDWR);    if (!fp) {        printf("Open error!\n");        return -1;    }    while (1) {        lseek(fp,0,SEEK_SET);        /*读出设备*/        ret = read(fp, Buf, sizeof(Buf));        printf("%d bytes read\n", ret);            /*检测结果*/        printf("BUF: %s\n",Buf);        printf("please input new data:\n");        gets(Buf);        lseek(fp,0,SEEK_SET);        /*写入设备*/        ret = write(fp, Buf, strlen(Buf));        printf("%d byte written\n", ret);    }     return 0;    }

3 编译、挂载、建立设备节点

$make#insmod [filename].ko#mknod /dev/my_dev c 255 0#chmod 777 /dev/my_dev

函数分析

设备号

#define MINORBITS    20#define MINORMASK    ((1U << MINORBITS) - 1)#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

register_chrdev_region

int register_chrdev_region(dev_t from, unsigned count, const char *name){    ……    for (n = from; n < to; n = next) {        next = MKDEV(MAJOR(n)+1, 0);        if (next > to)            next = to;        cd = __register_chrdev_region(MAJOR(n), MINOR(n),                   next - n, name);    ……

从中我们可以看到,register_chrdev_region即是把from开始连续count个设备号都注册。

函数 __register_chrdev_region() 主要执行以下步骤:

  1. 分配一个新的 char_device_struct 结构,并用 0 填充。
  2. 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序号。所以动态分配的主设备号总是小于 256,如果每个桶都有字符设备编号了,那动态分配就会失败。
  3. 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
  4. 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
  5. 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。

参考网站:http://blog.csdn.net/tigerjibo/article/details/6412672

cdev_init

void cdev_init(struct cdev *cdev, const struct file_operations *fops){    memset(cdev, 0, sizeof *cdev);    INIT_LIST_HEAD(&cdev->list);    kobject_init(&cdev->kobj, &ktype_cdev_default);    cdev->ops = fops;}

kobj是一个嵌入在该结构中的内核对象。它用于该数据结构的一般管理。

cdev_add

int cdev_add(struct cdev *p, dev_t dev, unsigned count){    int error;    p->dev = dev;    p->count = count;    error = kobj_map(cdev_map, dev, count, NULL,             exact_match, exact_lock, p);    if (error)        return error;    kobject_get(p->kobj.parent);    return 0;}

linux 内核中所有字符设备都记录在一个 kobj_map 结构的 cdev_map 散列表里。
cdev_add()函数中的 kobj_map() 函数就是用来把设备号及 cdev 结构一起保存到
cdev_map 散列表里。当以后要打开这个字符设备文件时,通过调用 kobj_lookup() 函数,
根据设备号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。

cdev_del

void cdev_del(struct cdev *p){    cdev_unmap(p->dev, p->count); //调用 kobj_unmap()释放 cdev_map散列表中的对象    kobject_put(&p->kobj);  ////释放 cdev结构本身}

unregister_chrdev_region

void unregister_chrdev_region(dev_t from, unsigned count){    dev_t to = from + count;    dev_t n, next;    for (n = from; n < to; n = next) {        next = MKDEV(MAJOR(n)+1, 0);        if (next > to)            next = to;        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));    }}

参考网站:
简单字符设备驱动:https://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html
关于private_data:http://blog.csdn.net/fivedoumi/article/details/50915634
linux char_dev源码分析 https://www.cnblogs.com/morris-tech/p/3918013.html

原创粉丝点击