怎么做转载小说网站/seo研究中心qq群
六、字符设备驱动
6.1、基本概念
-
Linux操作系统思想:一切皆文件
-
-
对设备的操作,也是将设备抽象成设备文件,应用层通过文件IO对设备文件进行操作,其最终结
果,是在操作设备。 -
Linux操作系统,将设备分成三大类:
- 字符设备: 除了网络设备和块设备,剩下的全部是字符设备
- 特点:I/O传输过程中以字符为单位进行传输。用户对字符设备发出读/写请求时,实际的硬件读/写操作一般紧接着发生(同步发生、无缓存机制)
-
- 块设备:存储设备
- 特点:数据传输以块(内存缓冲)为单位传输;用户对块设备读/写时,硬件上的读/写操作不会紧接着发生,即用户请求和硬件操作是异步的(异步、有缓存机制)
-
- 网络设备: 网卡等
- 特点:网络设备是一类特殊的设备,它不像字符设备或块设备那样通过对应的设备文件访问,也不能直接通过read或write进行数据请求,而是通过socket接口函数进行访问
- 网络设备: 网卡等
- 块设备:存储设备
- 字符设备: 除了网络设备和块设备,剩下的全部是字符设备
-
6.2、字符设备驱动框架
- 1、为驱动工程师提供管理驱动设备的统一架构
- 2、为用户层提供统一的访问控制设备的接口
6.2.1、字符设备框架中的数据类型结构体
-
字符设备结构体
在/include/linux/下的
cdev.h
中定义
struct cdev{struct kobject kobj; //kobject对象struct module *owner; //THIS_MODULEconst struct file_operations *ops; //操作方法集struct list_head list;dev_t dev; //设备号(U32 32bit无符号整型)unsigned int count; //设备引用计数
};
-
dev_t类型的设备号:由主设备号(高12bit)和次设备号(低20bit)组成:
-
MKDEV(major,minor)
宏函数:由主设备号major和次设备号minor生成设备号 -
MAJOR(dev)
宏函数:从设备号中提取主设备号 -
MINOR(dev)
宏函数:从设备号中提取次设备号#define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MKDEV(major,minor) (((major) << MINORBITS) | (minor)) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
设备操作结构体
struct file_operations{struct module *owner;loff_t(*llseek)(struct file*,loff_t,int);ssize_t(*read)(struct file*,char __user*,size_t,loff_t*);ssize_t(*write)(struct file*,constchar __user*,size_t,loff_t*);unsignedint(*poll)(struct file*,struct poll_table_struct*);long(*unlocked_ioctl)(struct file*,unsigned int,unsigned long);int(*mmap)(struct file*,struct vm_area_struct*);int(*open)(struct inode*,struct file*);int(*release)(struct inode*,struct file*);int(*fasync)(int,struct file*,int);...
};
6.2.2、为结构体变量分配空间
使用时需要添加返回值判断语句,失败时返回空。
/***cdev_alloc()-allocate a cdev structure**Allocates and returns a cdev structure,or NULL on failure.*/struct cdev *cdev_alloc(void)
6.2.3、初始化cdev结构体
/*** cdev_init() -initialize a cdev structure* @cdev: the structure to initialize* @fops: the file_operations for this device** Initializes @cdev,remembering @fops,making it ready to add to the* system with cdev_add().*/void cdev_init(struct cdev *cdev, const struct file_operations *fops)
6.2.4、添加cdev到系统中,由内核统一管理
使用时需要添加返回值判断语句:错误时返回负值。
/*** cdev_add() -add a char device to the system* @p: the cdev structure for the device* @dev: the first device number for which this device is responsible* @count: the number of consecutive minor numbers corresponding to this device** cdev_add() adds the device represented by @p to the system,making it* live immediately.Anegative error code is returned on failure.*/int cdev_add(struct cdev *p, dev_t dev, unsigned count)
设备号是系统资源:使用设备号,需要向内核申请,不使用的时候,需要释放
申请设备号和释放设备号的方法由内核提供:
/**alloc_chrdev_region() -register a rangeofchar device numbers*@dev: output parameter for first assigned number*@baseminor:first of the requested range of minor numbers*@count: the number of minor numbers required*@name: the name of the associated device or driver**Allocates a range of char device numbers.The major number will be*chosen dynamically,and returned(along with the first minor number)*in @dev. Returns zero or a negative error code.*/
//特点:主设备号未定,需由系统分配时使用该函数,次设备号由baseminor和count共同指定
int alloc_chrdev_region(dev_t* dev, unsigned baseminor, unsigned count, const char *name)/***register_chrdev_region()- register a range of device numbers*@from: the first in the desired range of device numbers; must include the major number.*@count:the number of consecutive device numbers required*@name:the name of the device or driver.**Return value is zero on success,a negative error code on failure.*///特点:主次设备号已经确定(0~255之间),用来注册多个次设备号时可用此函数
int register_chrdev_region(dev_t from, unsigned count, const char *name)/*
*unregister_chrdev_region() -return a range of device numbers
*@from: the first in the range of numbers to unregister
*@count: the number of device numbers to unregister
*
*This function will unregister a range of @count device numbers,
*starting with @from. The caller should normally be the one who
*allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
6.2.5、cdev_del
当不使用的时候,从内核中删除
/*
*cdev_del() -remove a cdevfromthe system
*@p:the cdev structure to be removed
*
*cdev_del() removes @p from the system, possibly freeing the structure itself.
*/void cdev_del(struct cdev *p)
{cdev_unmap(p->dev, p->count);kobject_put(&p->kobj);
}
6.3、编写字符设备驱动框架
#头文件static int __init demo_init(void)
{//0、申请设备号//1、分配cdev结构体//2、初始化cdev结构体//3、添加到内核中,由内核统一管理return0;
}static void __exit demo_exit(void)
{//1. cdev从内核中删除//2. 设备号资源释放
}module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
6.4、设备文件
6.4.1、创建设备文件
通过shell命令——mknod
创建设备文件:
mknod - make block or character special files
格式:mknod NAME TYPE [设备号]NAME:设备文件(节点)名TYPE:b:块设备c:字符设备
6.4.2、生成设备文件
- class_create()
/* This is a #define to keep the compiler from merging different*instances of the __key variable*/
#define class_create(owner,name)\
({\static struct lock_class_key __key;\__class_create(owner,name, &__key);\
})
/** owner: THIS_MODULE* name: 名字* 返回值 struct class* 指针*//**__class_create- create a structclassstructure最终的体现:在/sys/class目录中创建一个以*@owner: pointer to the module that is to"own"thisstructclass*@name: pointer to a stringforthe nameofthisclass.*@key: the lock_class_keyforthisclass;used by mutex lock debugging**This is used to create a struct class pointer that can then be used*in calls to device_create().**Returns & struct class pointer on success,or ERR_PTR() on error.**Note,the pointer created here is to be destroyed when finished by*making a call toclass_destroy().*/
struct class *__class_create(struct module* owner, const char* name, struct lock_class_key*key)/** 为了匹配数据类型*/static inline void* __must_check ERR_PTR(long error)
{return (void*)error;
}static inline long __must_check PTR_ERR(const void *ptr)
{return (long)ptr;
}long __must_check IS_ERR(const void *ptr) //判断是否执行错误void class_destroy(struct class *cls); //销毁2、device_create/**
*device_create-creates a device and registers itwithsysfs
*@class:pointer to the structclassthatthisdevice should be registered to
*@parent:pointer to the parent struct deviceofthisnewdevice,ifany
*@devt:the dev_tforthe char device to be added
*@drvdata:the data to be added to the deviceforcallbacks
*@fmt:stringforthe device's name
*
*Thisfunctioncan be used by char device classes.Astruct device
*will be createdinsysfs,registered to the specifiedclass.
*
*A"dev"file will be created,showing the dev_tforthe device,if
*the dev_t is not0,0.
*If a pointer to a parent struct device is passedin,the newly created
*struct device will be a childofthat deviceinsysfs.
*The pointer to the struct device will be returnedfromthe call.
*Any further sysfs files that might be required can be created usingthis
*pointer.
*
*Returns&struct device pointer on success,orERR_PTR()on error.
*
*Note:the structclasspassed tothisfunctionmust have previously
*been createdwitha call toclass_create().
*/struct device* device_create(struct class *class, struct device *parent,\dev_t devt, \void* drvdata,\constchar *fmt,...)void device_destroy(struct class *class, dev_t devt)
————————–以上是clasee_create和device_create所作的事————————-
-
完整代码如下:
/*file name : chardev.c*/ #include <linux/module.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h>#define NAME "chardev" #define COUNT 3 dev_t dev_no; struct cdev *cdevp = NULL; struct class *cls = NULL; struct device *devp = NULL;static int demo_open(struct inode *inode, struct file *fp) {printk(KERN_DEBUG "[%s-%s-%d]char_dev opened.\n", __FILE__, __func__, __LINE__ );return 0; }static int demo_release(struct inode *inode, struct file *fp) {printk(KERN_DEBUG "[%s-%s-%d]:chrdev exited.\n", __FILE__, __func__, __LINE__ );return 0; }struct file_operations fop = {.owner = THIS_MODULE,.open = demo_open,.release = demo_release, };static int __init demo_init(void) {int ret = 0;int i = 0;ret = alloc_chrdev_region(&dev_no, 0, COUNT, "NAME");if (ret < 0){printk(KERN_ERR "[%s-%s-%d]:alloc_chrdev_region failed.\n", __FILE__, __func__, __LINE__ );goto out0;}printk(KERN_ERR "dev_no:%d-%d",MAJOR(dev_no),MINOR(dev_no));cdevp = cdev_alloc();if (!cdevp){printk(KERN_ERR "[%s-%s-%d]:alloc_chrdev failed.\n", __FILE__, __func__, __LINE__ );ret = -ENOMEM;goto out1;}cdev_init(cdevp, &fop);ret = cdev_add(cdevp, dev_no, COUNT);if (ret < 0){goto out1;}cls = class_create(THIS_MODULE, NAME);if (IS_ERR(cls)){printk(KERN_ERR "[%s-%s-%d]class creat failed\n",__FILE__,__func__,__LINE__);ret = PTR_ERR(cls);goto out2;}for (i = 0; i < COUNT; i++){devp = device_create(cls, NULL, MKDEV(MAJOR(dev_no),i), NULL, "%s%d",NAME,i);if (IS_ERR(devp)){printk(KERN_ERR "[%s-%s-%d]device%d create failed\n",__FILE__,__func__,__LINE__,i);ret = PTR_ERR(devp);goto out3;}}return 0;out3:for (--i; i>0; i--){device_destroy(cls,MKDEV(MAJOR(dev_no),i));}class_destroy(cls); out2:cdev_del(cdevp); out1:unregister_chrdev_region(dev_no, COUNT); out0:return ret;}static void __exit demo_exit(void) {int i = 0;for (i = 0; i > COUNT; i++){device_destroy(cls,MKDEV(MAJOR(dev_no),i));}class_destroy(cls);cdev_del(cdevp);unregister_chrdev_region(dev_no, COUNT); }module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE("GPL");
KERN_DIR = /usr/src/linux-headers-4.4.0-040400-genericall:make -C $(KERN_DIR) M=`pwd` modules clean:make -C $(KERN_DIR) M=`pwd` cleanrm -rf modules.orderobj-m += chardemo.o
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FffOGp6D-1648824618616)(https://gitee.com/leon_code/pics_leon/raw/master/202203201902098.png)]
ubuntu 使用 udev 创建设备文件,也就是依赖于图中uevent文件中的内容。
默认创建在/dev目录中
附录:内核错误码
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#define EMFILE 24 /* Too many open files /
#define ENOTTY 25 / Not a typewriter /
#define ETXTBSY 26 / Text file busy /
#define EFBIG 27 / File too large /
#define ENOSPC 28 / No space left on device /
#define ESPIPE 29 / Illegal seek /
#define EROFS 30 / Read-only file system /
#define EMLINK 31 / Too many links /
#define EPIPE 32 / Broken pipe /
#define EDOM 33 / Math argument out of domain of func /
#define ERANGE 34 / Math result not representable */