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
typedef __kernel_dev_t dev_t;
#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>)的结构来代表字符设备;在内核调用设备操作前,需要分配并注册一个或几个这些结构 - 两中方法分配和初始化化这些结构
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
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);
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 typeread和write方法的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)

#ifndef _SCULL_H
#define _SCULL_H
#include <linux/cdev.h>
#include <linux/kernel.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
#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 NUM 4
#define DEVNAME "scull"
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);
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
#!/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"
#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 参考链接