Linux驱动开发学习-03.设备编号

本文深入探讨了Linux字符设备驱动的原理与实现,包括设备编号的管理、关键数据结构的作用、设备注册流程以及操作函数集的使用。通过具体示例scull设备的实现,详细讲解了如何在用户空间与内核空间之间进行数据交换。

03.设备编号

3.1 设备编号

3.1.1 主次编号

  • ls -l命令:字符驱动的第一列以c标识,块设备的第一列以b标识;,左侧的数字为主编号,右侧为次编号
  • 主编号标识设备相连的驱动,次编号被内核用来决定引用哪个设备
#root@OpenWrt:~# ls -l /dev
drwxr-xr-x    3 root     root            60 Jan  1  1970 bus
crw-------    1 root     root        5,   1 Jan  1  1970 console
  • dev_t类型(<linux/types.h>)用来持有设备编号,可使用<linux/kdev_t.h>中宏定义获取设备主次编号(MAJOR(dev_t dev)MINOR(dev_t dev));主次编号可以使用MKDEV(int major, int minor)转换为dev_t
//linux/types.h
typedef __kernel_dev_t		dev_t;
//linux/kdev_t.h
#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

3.1.2 分配和释放设备编号

  • 两种方式:动态&静态分配(<linux/fs.h>)
/*静态分配*/
int register_chrdev_region(dev_t from, unsigned count, const char *name);
/*动态分配*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
/*释放设备编号*/
void unregister_chrdev_region(dev_t from, unsigned count)
  • 静态分配的设备可从Documentation/devices.txt中查看,可随机选取空闲的编号作为主编号,但可能引发冲突
#Documentation/devices.txt截取片段
0		Unnamed devices (e.g. non-device mounts)
		  0 = reserved as null device number
		See block major 144, 145, 146 for expansion areas.

  1 char	Memory devices
		  1 = /dev/mem		Physical memory access
		  2 = /dev/kmem		Kernel virtual memory access
		  3 = /dev/null		Null device
  • 建议动态获取主设备编号,动态分配的缺点为无法提前创建设备节点,但可以在编号分配之后读取/proc/devices获取设备节点
root@OpenWrt:~# cat /proc/devices 
Character devices:
  1 mem
  4 ttyS
  5 /dev/tty
  5 /dev/console

Block devices:
259 blkext

3.2 三个重要的数据结构

3.2.1 file_operations

  • struct file_operations将字符驱动与设备操作相联系,f_op是一个指向file_operations的指针,而file_operations是一个函数指针的集合,每打开一个文件都会使用一个f_op成员与自身函数集合相关联(这也是内核中面向对象式编程
  • file_operations结构或其变体的每个成员必须指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作留置为NULL
  • __user是一种注释,对于正常编译没有任何效果,但它可以被外部检查软件使用,用来找出对用户空间地址的错误使用(一个指针是一个不能被直接解引用的用户空间地址)

3.2.2 file

  • struct file与用户空间程序的FILE指针无任何关系,它构代表一个打开的文件(它不特定给设备驱动,系统中每个打开的文件有一个关联的struct file在内核空间),它由内核在open 时创建,并传递给在文件上操作的任何函数,直到最后的关闭;在文件的所有实例都关闭后,内核释放这个数据结构

3.2.3 inode

  • inode结构用于内核在内部用表示文件,它和文件描述符的文件结构是不同的

3.3 字符设备注册

  • 内核在内部使用类型struct cdev<linux/cdev.h>)的结构来代表字符设备;在内核调用设备操作前,需要分配并注册一个或几个这些结构
  • 两中方法分配和初始化化这些结构
//获得一个独立的cdev结构
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;

//将cdev结构嵌入自己的设备特定的结构
void cdev_init(struct cdev *cdev, struct file_operations *fops);
//告诉内核,num是这个设备响应的第一个设备号,count是应当关联到设备的设备号的数目
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
//为系统去除字符设备
void cdev_del(struct cdev *dev);

3.4 操作函数集

3.4.1 open方法

  • 检查设备特定的错误(例如,设备没准备好, 或者类似的硬件错误)
  • 如果它第一次打开,初始化设备
  • 如果需要,更新f_op指针
  • 分配并填充要放进filp->private_data的任何数据结构
int (*open)(struct inode *inode, struct file *filp);

3.4.2 release方法

  • 释放open分配在filp->private_data中的任何东西
  • 在最后的close关闭设备
  • 不是每个close系统调用都会引起调用release方法,只有真正释放设备数据结构的调用会调用这个方法,因此当一个设备文件关闭次数超过它被打开的次数时,并不会出现任何问题(内核会维持一个文件结构被使用多少次的计数)
  • flush方法在每次应用程序调用close时都被调用

3.4.3 write & read方法

ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
  • filp是文件指针,count是请求的传输数据大小,buff参数指向持有被写入数据的缓存,或者放入新数据的空缓存,offp是一个指向long offset type对象的指针,它指出用户正在存取的文件位置,返回值是一个signed size type
  • readwrite方法的buff参数是用户空间指针,因此,它不能被内核代码直接解引用(内核空间与用户空间并不在一个RAM区),应当使用内核中拷贝函数进行拷贝(#include <asm/uaccess.h>
long copy_from_user(void *to,const void __user * from, unsigned long n);
long copy_to_user(void __user *to,const void *from, unsigned long n);

3.4.4 ioctl方法

3.5 实操(简版scull)

  • scull内存结构

在这里插入图片描述

  • scull.h
#ifndef _SCULL_H
#define _SCULL_H

#include <linux/cdev.h>
#include <linux/kernel.h>
//#include <linux/semaphore.h>
#include <uapi/asm-generic/fcntl.h>

#define SCULL_INIT 0
#define SCULL_QSET 2000
#define SCULL_QUANTUM 512

struct scull_qset {
    void **data;
    struct scull_qset *next;
};

struct scull_dev {
    struct scull_qset *data;
    int quantum;
    int qset;
    unsigned long size;
    unsigned int access_key;
    struct semaphore sem;
    struct cdev cdev;
};
#endif
  • scull.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <asm/uaccess.h>
#include <linux/semaphore.h>

#include "scull.h"

//#define DEBUG
#define NUM 4
#define DEVNAME "scull"

//DEFINE_SEMAPHORE(sem);

dev_t scull_devnum;
uint scull_major,scull_minor;
struct scull_dev scull_dev;

static void scull_trim(struct scull_dev *dev)
{
    int i;
    struct scull_qset *dptr ,*next;

    dptr = dev->data;
    for (; dptr != NULL ;dptr = next) {
        if (dptr->data) {
            for (i = 0 ;i < dev->qset ;i++) {
                kfree(dptr->data[i]);
            }
            kfree(dptr->data);
            dptr->data = NULL;
        }
        next = dptr->next;
        kfree(dptr);
    }
    dev->qset = SCULL_QSET;
    dev->quantum = SCULL_QUANTUM;
    dev->data = NULL;
    dev->size = 0;
}

static struct scull_qset *scull_follow(struct scull_dev *dev,unsigned int item)
{
    struct scull_qset *dptr = dev->data;

    if (!dptr) {
        dptr = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
        if (!dptr) {
            printk(KERN_DEBUG "kmalloc failed\n");
            return NULL;
        }
        memset(dptr, 0, sizeof(struct scull_qset));
    }
    while(item--) {
        if (!dptr->next) {
            dptr->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
            if (!dptr->next) {
                printk(KERN_DEBUG "kmalloc failed\n");
                return NULL;
            }
            memset(dptr->data, 0, sizeof(struct scull_qset));
        }
        dptr = dptr->next;
        continue;
    }
    return dptr;
}

#ifdef DEBUG
static void scull_mem_debug(struct scull_dev *dev)
{
    int i;
    struct scull_qset *dptr,*next;
        
    for (dptr = dev->data; dptr ;dptr = next) {
        if ((char *)dptr->data) {
            for (i = 0; i < dev->qset ;i++) {
                if ((char *)dptr->data[i]) {
                    printk(KERN_DEBUG "[i]: %s\n",(char *)dptr->data[i]);
                }
            }
        }
        next = dptr->next;
        continue;
    }
}
#endif

static int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev;

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev;
    
    if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        scull_trim(dev);
        up(&dev->sem);
    }
    return 0;
}

static ssize_t scull_read(struct file *filp, char __user *buffer, size_t count, loff_t *f_ops)
{
    struct scull_dev *dev;
    struct scull_qset *dptr;
    unsigned long itemsize;
    unsigned int qset,quantum;
    unsigned int s_pos,q_pos,reset,item;

    ssize_t retval = 0;

    dev = filp->private_data;
    qset = dev->qset;
    quantum = dev->quantum;
    itemsize = qset * quantum;

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    
    if (*f_ops > dev->size) {
        printk(KERN_DEBUG "can not read beyond data length\n");
        goto out;
    }
    if (*f_ops + count > dev->size) {
        count = dev->size - *f_ops;
    }
    
    item = *f_ops / itemsize;
    reset = *f_ops % itemsize;
    s_pos = reset / quantum;
    q_pos = reset % quantum;

#ifdef DEBUG
    scull_mem_debug(dev);
#endif
    dptr = scull_follow(dev,item);
    if (!dptr || !dptr->data || !dptr->data[s_pos]) {
        goto out;
    }
    if (count > quantum - q_pos) {
        count = quantum - q_pos;
    }
    if(copy_to_user(buffer, dptr->data[s_pos] + q_pos, count)) {
        printk(KERN_DEBUG "copy_to_user failed\n");
        goto out;
    }
    *f_ops += count;
    retval = count;
out:
    up(&dev->sem);
    return retval;
}

static ssize_t scull_write(struct file *filp, const char __user *buffer, size_t count, loff_t *f_ops)
{
    struct scull_dev *dev;
    struct scull_qset *dptr;
    unsigned long itemsize;
    unsigned int qset,quantum;
    unsigned int s_pos,q_pos,reset,item;

    ssize_t retval = -ENOMEM;

    dev = filp->private_data;
    qset = dev->qset;
    quantum = dev->quantum;
    itemsize = qset * quantum;

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

    item = *f_ops / itemsize;
    reset = *f_ops % itemsize;
    s_pos = reset / quantum;
    q_pos = reset % quantum;

    dptr = scull_follow(dev, item);
    if (!dptr)
        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;
        memset(dptr->data[s_pos], '\0', quantum);
    }
    if (count > quantum - q_pos)
        count = quantum - q_pos;
    if (copy_from_user(dptr->data[s_pos] + q_pos, buffer, count)) {
        retval = -EFAULT;
        goto out;
    }
    *f_ops += count;
    retval = count;
    
    if (dev->size < *f_ops)
        dev->size = *f_ops;
#ifdef DEBUG
    scull_mem_debug(dev);
#endif
out:
    up(&dev->sem);
    return retval;
}

static long scull_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
    return (long)0;
}

static loff_t scull_llseek(struct file *filp, loff_t offset, int where)
{
    return 0;
}

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

struct file_operations scull_ops = {
    .owner            = THIS_MODULE,
    .open            = scull_open,
    .read            = scull_read,
    .write            = scull_write,
    .unlocked_ioctl        = scull_ioctl,
    .llseek            = scull_llseek,
    .release        = scull_release,
};

static int scull_init_dev(void)
{
    int ret;
    if (scull_major) {
        ret = register_chrdev_region(MKDEV(scull_major,0), 1, DEVNAME);
        scull_devnum = MKDEV(scull_major,0);
    } else {
        ret = alloc_chrdev_region(&scull_devnum, 0, 1, DEVNAME);
        scull_major = MAJOR(scull_devnum);
        scull_minor = MINOR(scull_devnum);
    }

    if (ret)
        printk(KERN_ERR "register device failed\n");

    printk(KERN_DEBUG "register_chrdev_region success\n");
    return ret;
}

static void scull_setup_dev(struct scull_dev *dev)
{
    if (scull_init_dev()) {
        printk(KERN_DEBUG "scull_init_dev failed\n");
        unregister_chrdev_region(scull_devnum, 1);
    }
    dev->data = NULL;
    dev->qset = SCULL_QSET;
    dev->quantum = SCULL_QUANTUM;
    dev->size = 0;
    sema_init(&dev->sem, 1);
    cdev_init(&dev->cdev, &scull_ops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &scull_ops;
    if (cdev_add(&dev->cdev, scull_devnum, 1)) {
        printk(KERN_DEBUG "cdev add failed\n");
        unregister_chrdev_region(scull_devnum, 1);
    }
    printk(KERN_DEBUG "cdev_add success\n");
}

static int __init scull_init(void)
{
    scull_major = SCULL_INIT;
    scull_minor = SCULL_INIT;
    memset(&scull_dev,0,sizeof(struct scull_dev));
    scull_setup_dev(&scull_dev);
    printk(KERN_DEBUG "scull driver init\n");
    return 0;
}

static void __exit scull_exit(void)
{
    scull_trim(&scull_dev);
    cdev_del(&scull_dev.cdev);    
    unregister_chrdev_region(scull_devnum, 1);
    printk(KERN_DEBUG "scull driver exit\n");
}
MODULE_AUTHOR("joker");
MODULE_LICENSE("GPL v2");
module_init(scull_init);
module_exit(scull_exit);
  • Makefile
obj-m := scull.o   
KERNELDIR := /home/fa/linux-4.2.1
PWD := $(shell pwd)   

modules:  
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules  
modules_install:  
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install  
clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
  • install.sh
#!/bin/bash

mkdev() {
    make clean
    make
    devid=`cat /proc/devices | grep $1 | awk '{print $1}'`
    #if scull exit,rmmod it and insmod it again
    [ $devid ] && sudo rmmod $1
    sudo insmod $1.ko
    devid=`cat /proc/devices | grep $1 | awk '{print $1}'`
    [ -e /dev/$1 ] && sudo rm -rf /dev/$1
    sudo mknod /dev/$1 c $devid 0
}
mkdev "scull"
  • test.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <pthread.h>

#define DEVPATH "/dev/scull"
#define BUFSIZE 256
#define COUNTS 0

pthread_mutex_t mutex;

static void *read_routine(void *arg)
{
#if  1
    int rfd,i,counts;
    char rbuf[BUFSIZE] = {0};
    if ((rfd = open(DEVPATH,O_RDWR)) < 0) {
        printf("open %s failed (%s)\n",DEVPATH,strerror(errno));
        goto wait;
    }
    i = 0;
    counts = COUNTS;
    do {
        if (read(rfd, rbuf, sizeof(rbuf)) <= 0) {
            printf("read failed (%s)\n",strerror(errno));
            close(rfd);
            if ((rfd = open(DEVPATH, O_RDWR)) < 0) {
                printf("open %s failed (%s)\n",DEVPATH,strerror(errno));
                pthread_exit(NULL);
            }
            goto wait;
        }else {
                pthread_mutex_lock(&mutex);
                printf("\n");
                printf("[read:%d]: %s\n",i++,rbuf);    
                printf("\n");
                pthread_mutex_unlock(&mutex);
        }
        memset(rbuf, '\0', sizeof(rbuf));
    wait:
        usleep(1000000);
    } while(counts--);
#endif
}

static void *write_routine(void *arg)
{
#if 1
    int wfd,i,counts;
    char wbuf[BUFSIZE] = {0};

    for (i = 0; i < sizeof(wbuf); i++) {
        wbuf[i] = 'a' + i % 26;
    }
    if ((wfd = open(DEVPATH, O_RDWR)) < 0) {
        printf("open %s failed (%s)\n",DEVPATH,strerror(errno));
        pthread_exit(NULL);
    }
    i = 0;
    counts = COUNTS;
    do {
        if (write(wfd, wbuf, sizeof(wbuf)) != sizeof(wbuf)) {
            printf("write failed (%s)\n",strerror(errno));
            goto wait;
        }
        pthread_mutex_lock(&mutex);
        printf("\n");
        printf("[write:%d]: %s\n",i++,wbuf);
        printf("\n");
        pthread_mutex_unlock(&mutex);
    wait:
        usleep(1000000);
    
    } while(counts--);
#endif
}

int main(void)
{
    int i;
    pthread_t pid[2];
    if (access(DEVPATH,F_OK)) {
        printf("%s not exist\n",DEVPATH);
        return -1;
    }
    pthread_mutex_init(&mutex,NULL);
    for (i = 0; i < sizeof(pid)/sizeof(pthread_t); i++) {
        if (pthread_create(&pid[i], NULL, i == 0 ? read_routine:write_routine,NULL)) {
            printf("create thread failed\n");
            return -1;
        }
    }
    pthread_join(pid[0], NULL);
    pthread_join(pid[1], NULL);

    return 0;
}
  • 测试:gcc test.c -o test -lpthread && ./test

3.6 总结

  • dev_t在内核中表示设备号类型;主次编号(#include <linux/types.h>)
  • 三个重要的数据结构(#include <linux/fs.h>)
  • 静态/动态分配设备编号,设备号注册(#include<linux/fs.h>)
  • 字符设备注册(#include <linux/cdev.h>)
  • 用户空间与内核空间数据拷贝(#include <asm/uaccess.h>)

在这里插入图片描述

3.7 参考链接

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值