0

0

linux下字符设备有哪些

青灯夜游

青灯夜游

发布时间:2023-03-09 10:49:02

|

1970人浏览过

|

来源于php中文网

原创

linux字符设备有:1、鼠标,是计算机的一种外接输入设备,也是计算机显示系统纵横坐标定位的指示器;2、键盘,是用于操作计算机设备运行的一种指令和数据输入装置;3、串行端口终端,使用计算机串行端口连接的终端设备;4、控制终端;5、控制台等。

linux下字符设备有哪些

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

linux字符设备


字符设备是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 命令可以查看当前系统中所有的字符设备和块设备。

1.png

2.png

在Linux 中一切接文件,设备也被抽象成文件,在/dev/ 目录下可以查看到字符设备和块设备的对应的文件。
例如这是两个串口设备文件,使用ls -l 查看它的详细信息。
与普通文件不同的是设备文件没有大小,而是被替换成了设备号(主设备号和次设备号),设备号可以与/proc/devices 中的信息相对应。

3.png

如何访问一个设备?

既然它被抽象成了一个文件,那么自然就用文件IO (open、read、write 等等) 来访问。

设备号

4.png

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)

影缘版商城
影缘版商城

率先引入语言包机制,可在1小时内制作出任何语言版本,程序所有应用文字皆引自LANG目录下的语言包文件,独特的套图更换功能,三级物品分类,购物车帖心设计,在国内率先将购物车与商品显示页面完美结合,完善的商品管理,具备上架、下架缺货及特价商品设置功能多多,商城名、消费税、最低购物金额、货币符号、商城货币名称全部后台设定,多级用户考虑,管理员只需要设置用户级别、不同级别用户之返点系统自动判断用户应得返还

下载

释放设备号:
void unregister_chrdev_region(dev_t from, unsigned count)

字符设备结构体 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->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 注册。

5.png

还有一个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); }

struct file_operations

字符设备在/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/ 目录下生成一个新的文件夹,其中包含属于此类的设备文件夹。

5-1.png

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 		 //file_operations声明
#include     //module_init  module_exit声明
#include       //__init  __exit 宏定义声明
#include 	 //class  devise声明
#include    //copy_from_user 的头文件
#include      //设备号  dev_t 类型声明
#include           //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 的调用原理

6.png

相关推荐:《Linux视频教程

相关专题

更多
Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

6

2026.01.15

公务员递补名单公布时间 公务员递补要求
公务员递补名单公布时间 公务员递补要求

公务员递补名单公布时间不固定,通常在面试前,由招录单位(如国家知识产权局、海关等)发布,依据是原入围考生放弃资格,会按笔试成绩从高到低递补,递补考生需按公告要求限时确认并提交材料,及时参加面试/体检等后续环节。要求核心是按招录单位公告及时响应、提交材料(确认书、资格复审材料)并准时参加面试。

28

2026.01.15

公务员调剂条件 2026调剂公告时间
公务员调剂条件 2026调剂公告时间

(一)符合拟调剂职位所要求的资格条件。 (二)公共科目笔试成绩同时达到拟调剂职位和原报考职位的合格分数线,且考试类别相同。 拟调剂职位设置了专业科目笔试条件的,专业科目笔试成绩还须同时达到合格分数线,且考试类别相同。 (三)未进入原报考职位面试人员名单。

37

2026.01.15

国考成绩查询入口 国考分数公布时间2026
国考成绩查询入口 国考分数公布时间2026

笔试成绩查询入口已开通,考生可登录国家公务员局中央机关及其直属机构2026年度考试录用公务员专题网站http://bm.scs.gov.cn/pp/gkweb/core/web/ui/business/examResult/written_result.html,查询笔试成绩和合格分数线,点击“笔试成绩查询”按钮,凭借身份证及准考证进行查询。

6

2026.01.15

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

63

2026.01.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

34

2026.01.13

PHP 高性能
PHP 高性能

本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

74

2026.01.13

MySQL数据库报错常见问题及解决方法大全
MySQL数据库报错常见问题及解决方法大全

本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

20

2026.01.13

PHP 文件上传
PHP 文件上传

本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

31

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.2万人学习

Git 教程
Git 教程

共21课时 | 2.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号