Zu den Linux-Zeichengeräten gehören: 1. Maus, ein externes Eingabegerät für den Computer und ein Indikator für die Positionierung der vertikalen und horizontalen Koordinaten des Computeranzeigesystems. 2. Tastatur, ein Befehls- und Dateneingabegerät 3. Serieller Anschluss, ein über einen seriellen Anschluss angeschlossenes Endgerät; 5. Konsole usw.
Die Betriebsumgebung dieses Tutorials: Linux7.3-System, Dell G3-Computer.
Das Zeichengerät ist eines der drei Hauptgeräte in Linux (die anderen beiden sind Blockgeräte und Netzwerkgeräte). Sie werden alle im /dev-Verzeichnis des Dateisystems in Form eines Dateiknotens angezeigt (crw--w---- 1 root tty 4, 0 July 11 09:11 tty0, wobei c den Zeichengerätetyp darstellt).
Zeichengerät bezieht sich auf ein Gerät, das ohne Pufferung direkt lesen und schreiben kann, z. B. eine Maus, eine Tastatur, ein Gerät mit serieller Schnittstelle, ein Modem usw. Der Unterschied zwischen ihm und einem Blockgerät besteht darin, dass die Grundeinheit der Zeichenoperation Byte ist.
Klassifizierung von Zeichengeräten
Zu Zeichengeräten gehören hauptsächlich Steuerungsterminalgeräte und serielle Endgeräte wie Konsolen und Tastaturen. Basierend auf Unterschieden in Funktionen und Hardware werden Zeichenterminalgeräte wie folgt klassifiziert:
Terminal mit serieller Schnittstelle (/dev/ttSn): ein Terminalgerät, das über eine serielle Schnittstelle eines Computers angeschlossen ist. Die Datenübertragungsmethode für serielle Geräte ist dieselbe 8 Bits werden in einer einzigen Zeile übertragen. Geben Sie echo 'hello world' > /dev/ttyS0 in die Befehlszeile ein, um die Eingabe auf das entsprechende Gerät zu schreiben.
Pseudo-Terminal (/dev/ttyp, /dev/ptyp): Da es auf der untersten Ebene kein echtes Hardwaregerät gibt, wird es verwendet, um eine Schnittstelle im Terminalstil für andere Programme bereitzustellen, z zwischen dem Netzwerkserver und dem Shell-Programm, wenn Sie sich über die Netzwerk-Terminalschnittstelle beim Host anmelden.
Steuerterminal (/dev/tty): Die Hauptgerätenummer ist 5. Das Prozesssteuerterminal ist dem Prozess zugeordnet. Der Login-Shell-Prozess verwendet beispielsweise das Terminal /dev/tty.
Console (/dev/ttyn, /dev/consol): Der Monitor für die Computereingabe und -ausgabe wird tty1 verwendet, während die grafische Benutzeroberfläche von Ubuntu tty7 verwendet.
Andere Typen: Aktuelles Linux verfügt über viele andere Arten von Gerätespezialdateien für viele verschiedene Geräte, wie zum Beispiel das /dev/ttyIn-Gerät des ISIDIN-Geräts.
Die Art und Eigenschaften von Zeichengeräten
Zeichengeräte sind eine Art Gerätedateisystem, das den logischen Gerätedateien entspricht, die von der zugrunde liegenden Hardware für die obere Schicht bereitgestellt werden, genau wie beim Verbinden Ein Datenport (Datenregister) mit Wenn eine Datei verbunden ist, arbeitet der Gerätetreiber direkt an der Datei, sodass er direkt Lese- und Schreibvorgänge am Port ausführt. Auch als Datei muss der Zeichengerätetreiber auch die grundlegenden Operationen von Dateien wie open(), close(), write(), read() usw. implementieren. Natürlich werden auch Terminalumleitungsoperationen unterstützt.
Zeichengerätedateidateien werden in Einzelbyte-Einheiten gelesen und geschrieben, und es ist nicht erforderlich, einen Hardwarepuffer einzurichten. Der Zugriff auf das Gerät erfolgt durch das Betriebssystem als Bytestream. Der Byte-Stream ähnelt dem Einrichten einer Übertragungspipe zwischen dem Hardware-Port und dem Dateisystem. Bytes werden einzeln durch die Pipe übertragen und sowohl Lesern als auch Schreibern präsentiert. Diese Streaming-Funktion ist im Treiber als Pufferwarteschlange implementiert. Beispiel: Die Lese- und Schreibpufferwarteschlange in der Struktur der Konsole
struct tty_struct { struct termios termios; int pgrp; int stopped; void (*write)(struct tty_struct * tty); struct tty_queue read_q; //读队列 struct tty_queue write_q; //写队列 struct tty_queue secondary; //tty辅助队列(存放规格化后的字符) };
Das Zeichengerät wird durch die Zeichengerätenummer identifiziert. Die Zeichengerätenummer besteht aus einer Hauptgerätenummer und einer Nebengerätenummer. Die Gerätenummer von /dev/ttyS0 ist beispielsweise (4, 64); Die Hauptgerätenummer muss eins zu eins mit dem Gerät und dem Treiber übereinstimmen. Im Allgemeinen wird die Nebengerätenummer vom Treiber für den intern verwendeten Code verwendet, um zwischen Gerätedetails zu unterscheiden des Kernels.
cat /proc/devices
Mit dem Befehl können alle Zeichengeräte angezeigt und Geräte im aktuellen System blockiert werden.
Unter Linux werden beim Verbinden von Dateien auch Geräte in Dateien abstrahiert. Die entsprechenden Dateien für Zeichengeräte und Blockgeräte können im Verzeichnis /dev/ eingesehen werden.
Dies sind beispielsweise zwei Gerätedateien für serielle Ports. Verwenden Sie ls -l, um die Details anzuzeigen.
Im Gegensatz zu gewöhnlichen Dateien haben Gerätedateien keine Größe, sondern werden durch Gerätenummern (Hauptgerätenummern und Nebengerätenummern) ersetzt. Die Gerätenummern können den Informationen in /proc/devices entsprechen.
Wie greife ich auf ein Gerät zu?
Da es in eine Datei abstrahiert ist, ist es selbstverständlich, Datei-E/A (Öffnen, Lesen, Schreiben usw.) zu verwenden, um darauf zuzugreifen.
dev_t dev = MKDEV(major,minor)
major = MAJOR(dev)
minor = MINOR(dev)
设备号由major和minor 组成,总共32位,高12位为major,低20位为minor。每一个设备号都唯一对应着一个cdev结构体。
注册字符设备首先要申请设备号,可以一次批量的申请多个设备号(相同的major,依次递增的minor)。
如果没有指定主设备号的话就使用如下函数来申请设备号:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
baseminor:起始的minor值。
count:一共申请几个设备号。申请到的设备号在(MKDEV(major,baseminor) ~ MKDEV(major,baseminor+count)) 范围内。
(自动申请设备号的原理,其实是传递一个major = 0,然后由内核分配一个空闲的设备号返回)
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:int register_chrdev_region(dev_t from, unsigned count, const char *name)
释放设备号:void unregister_chrdev_region(dev_t from, unsigned count)
//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->ops,即cdev->ops = &xxx_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, const void *from, unsigned long n)
static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n)
返回非0 表示错误。
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件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, const char *fmt, ...);
摧毁一个设备:extern void device_destroy(struct class *cls, dev_t devt);
创建类会在/sys/class/ 目录下生成一个新的文件夹,其中包含属于此类的设备文件夹。
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");
应用open到file_operations->open 的调用原理
相关推荐:《Linux视频教程》
Das obige ist der detaillierte Inhalt vonWas sind die Zeichengeräte unter Linux?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!