热搜:前端 nest neovim nvim

下列设备是字符设备(常见的字符设备)

lxf2023-09-05 08:00:01

Linux字符设备包括:1。鼠标,是计算机的外部输入设备,是计算机显示系统纵横坐标定位的指示器;2.键盘是操作计算机设备的指令和数据输入装置;3.串口终端,通过计算机串口连接的终端设备;4.控制终端;5、控制台等。

下列设备是字符设备(常见的字符设备)

本教程的操作环境:linux7.3系统和Dell G3电脑。

linux字符设备


字符设备是Linux中的三大设备之一(另外两个是块设备和网络设备)。它们都在文件系统的/dev目录下显示为一个文件节点(crw-w-1 roottty4,0 July 11 09:11 tty0其中c代表字符设备类型)。

字符设备是指不需要缓冲就可以直接读写的设备,如鼠标、键盘、串口设备、调制解调器等。它不同于块设备,因为字符操作的基本单位是字节。

字符设备分类

字符设备主要包括控制终端设备和串行终端设备,如控制台和键盘。根据功能和硬件的不同,字符终端设备分类如下:

  • 串口终端(/dev/ttSn):计算机串口连接的终端设备。串行设备的数据传输方式是8位单线传输,字符相同。输入echo & # 39你好世界& # 39;& gt/dev/ttyS0可以将输入写入相应的设备。

  • 伪终端(/dev/ttyp,/dev/ptyp):对应的底层没有真正的硬件设备,用来为其他程序提供终端式的接口,比如网络登录主机时网络服务器与shell程序之间的终端接口。

    控制终端(/dev/tty):主设备号为5,进程控制终端,终端与进程关联。例如,终端/dev/tty由登录shell进程使用。

    控制台(/dev/ttyn,/dev/consol):计算机输入输出显示。控制台登录时使用tty1,ubuntu图形界面使用tty7。

    其他类型:目前的linux还有很多其他针对不同设备的特殊文件,比如ISIDIN设备的/dev/ttyIn设备。

    字符设备的属性和特征

  • 字符设备是一种设备文件系统,相当于底层硬件提供给上层的逻辑设备文件,就像一个数据端口(数据寄存器)对接一个文件,设备驱动直接操作文件,所以直接读写端口。作为一个文件,字符设备驱动还必须实现文件的基本操作,如open()、close()、write()、read()等。当然也支持终端重定向操作。

  • 字符设备文件的读写是以单字节为单位的,不需要设置硬件缓冲区。操作系统像字节流一样访问设备。字节流就像是在硬件端口和文件系统之间搭建了一个传输管道,字节通过管道一个一个的传输,呈现给读写双方。这个流特性是作为驱动程序中的缓冲队列实现的。比如控制台结构中的读写缓冲队列。

    struct tty _ struct { struct termios termios; int pgrp; int已停止; void(* write)(struct tty _ struct * tty); struct tty _ queue read _ q;//读队列 struct tty _ queue write _ q;//写队列 struct tty _ queue secondary;//tty辅助队列(存储规范化字符) };
    字符设备由字符设备编号标识。字符设备号由一级设备号和二级设备号组成,例如/dev/ttyS0的设备号为(4,64);主设备号标识设备对应的驱动,内核通过主设备号将设备与驱动一一对应。辅助设备号由驱动程序用来区分驱动程序内部的设备细节所使用的代码,它不被内核的其他部分使用。

    字符设备如何在应用层体现

    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);

    struct class { const char *name; struct module *owner; struct class_attribute *class_attrs; const struct attribute_group **dev_groups; struct kobject *dev_kobj; int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); char *(*devnode)(struct device *dev, umode_t *mode); void (*class_release)(struct class *class); void (*dev_release)(struct device *dev); int (*suspend)(struct device *dev, pm_message_t state); int (*resume)(struct device *dev); const struct kobj_ns_type_operations *ns_type; const void *(*namespace)(struct device *dev); const struct dev_pm_ops *pm; struct subsys_private *p; };

    创建类后创建设备,使用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的其他相关文章!