Linux字符设备包括:1。鼠标,是计算机的外部输入设备,是计算机显示系统纵横坐标定位的指示器;2.键盘是操作计算机设备的指令和数据输入装置;3.串口终端,通过计算机串口连接的终端设备;4.控制终端;5、控制台等。
本教程的操作环境:linux7.3系统和Dell G3电脑。
linux字符设备
字符设备是Linux中的三大设备之一(另外两个是块设备和网络设备)。它们都在文件系统的/dev目录下显示为一个文件节点(crw-w-1 roottty4,0 July 11 09:11 tty0其中c代表字符设备类型)。
字符设备是指不需要缓冲就可以直接读写的设备,如鼠标、键盘、串口设备、调制解调器等。它不同于块设备,因为字符操作的基本单位是字节。
字符设备分类
字符设备主要包括控制终端设备和串行终端设备,如控制台和键盘。根据功能和硬件的不同,字符终端设备分类如下:
cat /proc/devices命令可以查看当前系统中的所有字符设备和块设备。
在Linux中,所有连接的文件和设备也被抽象成文件,在/dev/目录中可以查看到字符设备和块设备对应的文件。
例如,这是两个串行设备文件。使用ls -l查看其详细信息。
与普通文件不同,设备文件没有大小,而是用设备号(主设备号和辅设备号)来代替,可以对应/proc/devices中的信息。
如何访问设备?
由于它被抽象成一个文件,所以使用文件IO(打开、读取、写入等)是很自然的。)来访问它。
设备编号
dev_t dev = MKDEV(major,MINOR)
MAJOR = MAJOR(dev)
MINOR = MINOR(dev)
设备编号由主要和次要组成,共32位,其中高12位为主要,低20位为次要。每个设备号唯一地对应于一个cdev结构。
注册角色设备,首先要申请设备号,可以一次批量申请多个设备号(同一个专业,依次递增辅修)。
如果没有指定主设备号,使用下面的函数申请设备号:
int alloc _ chrdev _ region(dev _ t * dev,unsigned baseminor,unsigned count,constchar * name)
baseminor:初始值。
统计:申请了多少个设备号?应用的设备号在(mkdev (major,base minor) ~ mkdev (major,base minor+count)范围内。
(自动申请设备号的原理其实就是传一个major = 0,然后内核分配一个空空闲的设备号返回)
如果给定了一个设备的主设备号和次设备号,使用下面的函数注册设备号:
int register _ chrdev _ region(dev _ t from,unsigned count,constchar * name)。
释放设备号:
void unregister _ chrdev _ region(dev _ tfrom,无符号计数)
字符设备结构cdev
//include/linux/cdev.h struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };常用
申请一个cdev内存:
Struct CDEV * CDEV _ Alloc(void);
初始化cdev-& gt;Ops,即cdev-& gt;ops = & ampxxx _ file _ operation:
void cdev _ init(struct cdev *,const struct file _ operations *);
将填充的cdev实例添加到cdev链表中。意味着向内核注册一个字符设备:
int cdev _ add (struct cdev *,dev _ t,unsigned);//dev_t:添加cdev时,必须传递一个dev_t,并且是cdev唯一的。
在添加cdev _ add期间,CDEV和dev_t将被绑定。
从内核中删除一个字符设备:
void cdev _ del(struct cdev *);
不常用
增加cdev调用计数:
Void CDEV _ put(struct CDEV * p);
总结:注册角色设备的主要流程是申请设备号dev_t,创建一个cdev,初始化CDEV(CDEV->;Ops),将cdev添加到内核中(同时绑定cdev和dev_t)。
如果你觉得这些步骤太麻烦,内核还提供了一步注册字符设备的功能-_——_ _ register _ chrdev
。它将申请多个设备号,创建一个cdev并初始化它,然后将同一个cdev注册与这多个设备号绑定。
还有一个register_chrdev,是__register_chrdev的封装。辅助设备号从基值0开始,直接应用256个辅助设备号。
static inline int register _ chrdev(unsigned int major,const char *name,const struct file _ operations * fops){ return _ _ register _ chrdev(major,0,256,name,fops);}
结构文件操作
字符设备在/dev/目录下创建设备文件,通过struct file_operations向应用层提供控制接口。应用层对应的open、read等函数会被file_operations对应的函数调用。
//include/linux/fs.h 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 *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*mremap)(struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };copy_to_user()和copy_from_user()
出于安全考虑,应用进程不能直接访问内核数据,所以需要借助这两个函数进行复制:
Static inline int copy _ to _ user(void _ _ user volatile * to,constvoid * from,Unsigned long n)
Static inline int copy _ from _ user(void * to,constvoid _ _ user volatile * from,Unsigned long n)
返回一个表示错误的非零值。
自动创建设备文件
设备节点的自动创建是在驱动的entry函数中完成的,自动创建设备节点的相关代码一般是在cdev_add函数之后添加的。首先,创建一个类,它是一个结构,在文件include/Linux/device.h中定义。
使用class_create创建一个类:
extern struct class * __must_check __class_create(struct module *owner, const char *name, struct lock_class_key *key); #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })使用class_destroy销毁一个类:
extern void class _ destroy(struct class * CLS);
创建类后创建设备,使用device_create创建设备:
struct device * device _ create(struct class * CLS,struct device * parent,dev _ t devt,void * drvdata,constchar * FMT,...);
销毁一个设备:
extern void device _ destroy(struct class * CLS,dev _ t devt);
创建类将在/sys/class/目录下生成一个新文件夹,其中包含属于该类的设备文件夹。
IS_ERR和PTR_ERR
IS_ERR可以判断一个指针是否是空,PTR_ERR将指针转换成数值并返回。
static inline long __must_check PTR_ERR(const void *ptr) { return (long) ptr; } static inline long __must_check IS_ERR(const void *ptr) { return IS_ERR_VALUE((unsigned long)ptr); }代码示例
#include <linux/fs.h> //file_operations声明 #include <linux/module.h> //module_init module_exit声明 #include <linux/init.h> //__init __exit 宏定义声明 #include <linux/device.h> //class devise声明 #include <linux/uaccess.h> //copy_from_user 的头文件 #include <linux/types.h> //设备号 dev_t 类型声明 #include <asm/io.h> //ioremap iounmap的头文件 #define DEVICE_CNT 0 //设备号个数 struct led_device{ dev_t devid; //设备号 int major; //主设备号 int minor; //次设备号 char* name = "led"; //驱动名 struct cdev led_dev; //cdev 结构体 struct class *class; /* 类 */ struct device* device; //设备 }; struct led_device led; static int led_open(struct inode *inode, struct file *filp) { return 0; } static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { return 0; } static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } /* 设备操作函数 */ static struct file_operations led_fo = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, }; static int _init led_init() { /*注册字符设备驱动*/ /*1.注册设备号*/ led.major = 0; //由内核自动分配主设备号 if(led.major) //如果分配了的话就注册 { led.devid = MKDEV(led.major,0); register_chrdev_region(led.devid,DEVICE_CNT,led.name); //将驱动注册到内核中 } else{ //如果没有分配的话 //从0号(次设备号)开始申请 alloc_chrdev_region(&led.devid,0,DEVICE_CNT,led.name); //申请设备号 led.major = MAJOR(led.devid); //获取主设备号 led.minor = MANOR(led.devid); //获取次设备号 } printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor); /*2.初始化 cdev 结构体*/ led.led_dev.woner = THIS_MODULE; cdev_init(&led.led_dev,&led_fo); //将操作函数初始化到cdev结构体 /*3.应该是向链表中添cdev*/ cdev_add(&led.led_dev,led.devid,DEVICE_CNT); /*4.创建节点*/ led.class = class_create(THIS_MODULE,led.name); //先创建一个类 led.device = device_create(led.class,NULL,led.devid,NULL); //创建设备 return 0; } static void _exit led_exit() { /* 注销字符设备驱动 */ cdev_del(&newchrled.cdev);/* 删除cdev */ unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */ device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); } /*注册字符设备入口与卸载入口*/ module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("zhoujianghong");将打开应用于文件操作-& gt;开放的呼叫原则
相关推荐:《Linux视频教程》
以上是linux下字符设备的详细情况。更多详情请关注AdminJS的其他相关文章!