linux字元設備有:1、滑鼠,是電腦的一種外接輸入設備,也是電腦顯示系統縱橫座標定位的指示器;2、鍵盤,是用來操作電腦設備運作的一種指令及資料輸入裝置;3、序列埠終端,使用電腦序列埠連接的終端設備;4、控制終端;5、控制台等。
本教學操作環境:linux7.3系統、Dell G3電腦。
字元設備是Linux三大設備之一(另外兩種是區塊設備,網路設備)。它們皆以一個檔案節點形式顯示在檔案系統的/dev目錄下(crw--w---- 1 root tty 4, 0 7月 11 09:11 tty0 其中c代表字元裝置類型)。
字元設備是指設備無需緩衝即可直接進行讀寫的設備, 如滑鼠,鍵盤,串列設備、調變解調器等, 它與區塊設備的區別在於是字元操作的基本單位是位元組.
字元設備的分類
字元設備主要包括控制終端設備和序列終端設備, 例如控制台和鍵盤。依據功能與硬體上的差異, 字元終端設備有下列分類:
序列埠終端(/dev/ttSn):使用電腦序列埠連接的終端設備,序列裝置資料傳輸方式為相同字元8個bit單線傳輸, 在指令列輸入echo 'hello world' > /dev/ttyS0可將輸入寫入對應裝置。
偽終端(/dev/ttyp,/dev/ptyp): 對應底層不存在真實的硬體設備, 用於為其他程式提供終端式樣的接口,如網路登陸主機時網路伺服器和shell程式之間的終端介面。
控制終端(/dev/tty):主設備號碼為5, 進程控制終端,與進程相關聯,如登陸shell進程所使用的是終端/dev/tty。
控制台(/dev/ttyn,/dev/consol): 電腦輸入輸出的顯示器,當控制台登陸時, 使用的就是tty1, 而ubuntu 圖形介面所使用的tty7 。
其他類型:現行的linux針對許多不同的裝置建有許多其他種類的裝置特殊文件,如ISIDIN裝置的/dev/ttyIn裝置。
字元設備的性質及特性
字元裝置屬於裝置檔案系統的一種, 相當於底層硬體向上層提供的邏輯設備文件, 宛如將一個資料埠(資料暫存器)與一個文件對接起來,設備驅動程式直接對文件操作, 於是便直接對埠進行了讀寫操作。同樣作為文件, 字元設備驅動也必須實現文件的基本的操作open(),close(),write(),read()等,當然終端重定向操作也是支援的。
字元裝置檔案檔案的讀寫是以單一位元組為單位的, 不需要設立硬體緩衝區。設備像存取位元組流一樣被作業系統存取。位元組流就像在硬體連接埠和檔案系統搭建起了一個傳送管道, 位元組逐個通過管道傳輸並呈現給讀寫雙方。這個流特性在驅動程式中是以緩衝佇列來實現的。例如: 控制台的結構體中的讀寫緩衝佇列
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辅助队列(存放规格化后的字符) };
#字元裝置由字元裝置號碼標識。字元設備號由主設備號和次設備號構成, 例如/dev/ttyS0的設備號為(4,64); 主設備號標識設備對應驅動程序, 內核透過主設備號將設備和驅動程序一一對應起來, 次設備號由驅動程式使用, 用於驅動程式內部區分設備細節差別使用的程式碼,內核其他部分不使用它。
cat /proc/devices
指令可以查看目前系統中所有的字元設備和區塊設備。
在Linux 中一切接文件,裝置也被抽象成文件,在/dev/ 目錄下可以查看到字元裝置和區塊設備的對應的檔案。
例如這是兩個串列埠設備文件,使用ls -l 查看它的詳細資訊。
與普通檔案不同的是裝置檔案沒有大小,而是被替換成了裝置號碼(主裝置號碼和次裝置號碼),裝置號碼可以與/proc/devices 中的資訊相對應。
如何存取一個裝置?
既然它被抽象化成一個文件,那麼自然就用文件IO (open、read、write 等等) 來存取。
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视频教程》
以上是linux下字元裝置有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!