分享更有价值
被信任是一种快乐

linux下有哪些字符设备

文章页正文上

这篇文章主要介绍“linux下有哪些字符设备”,在日常操作中,相信很多人在linux下有哪些字符设备问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”linux下有哪些字符设备”的疑惑有所帮助!接下来,请跟着小编一起来学习吧! linux字符设备有:1、鼠标,是计算机的一种外接输入设备,也是计算机显示系统纵横坐标定位的指示器;2、键盘,是用于操作计算机设备运行的一种指令和数据输入装置;3、串行端口终端,使用计算机串行端口连接的终端设备;4、控制终端;5、控制台等。字符设备是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()等,当然终端重定向操作也是支持的。字符设备文件文件的读写是以单个字节为单位的, 不需要设立硬件缓冲区。 设备像访问字节流一样被操作系统访问。 字节流就像在硬件端口和文件系统搭建起了一个传送管道, 字节逐个通过管道传输并呈现给读写双方。 这个流特性在驱动程序中是以缓冲队列来实现的。例如: 控制台的结构体中的读写缓冲队列

structtty_struct{
structtermiostermios;
intpgrp;
intstopped;
void(*write)(structtty_struct*tty);
structtty_queueread_q;//读队列
structtty_queuewrite_q;//写队列
structtty_queuesecondary;//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
structcdev{
structkobjectkobj;
structmodule*owner;
conststructfile_operations*ops;
structlist_headlist;
dev_tdev;
unsignedintcount;
};

常用申请一个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是唯一对应的。
cde免费云主机、域名v_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
structfile_operations{
structmodule*owner;
loff_t(*llseek)(structfile*,loff_t,int);
ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);
ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);
ssize_t(*read_iter)(structkiocb*,structiov_iter*);
ssize_t(*write_iter)(structkiocb*,structiov_iter*);
int(*iterate)(structfile*,structdir_context*);
unsignedint(*poll)(structfile*,structpoll_table_struct*);
long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);
long(*compat_ioctl)(structfile*,unsignedint,unsignedlong);
int(*mmap)(structfile*,structvm_area_struct*);
int(*mremap)(structfile*,structvm_area_struct*);
int(*open)(structinode*,structfile*);
int(*flush)(structfile*,fl_owner_tid);
int(*release)(structinode*,structfile*);
int(*fsync)(structfile*,loff_t,loff_t,intdatasync);
int(*aio_fsync)(structkiocb*,intdatasync);
int(*fasync)(int,structfile*,int);
int(*lock)(structfile*,int,structfile_lock*);
ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);
unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);
int(*check_flags)(int);
int(*flock)(structfile*,int,structfile_lock*);
ssize_t(*splice_write)(structpipe_inode_info*,structfile*,loff_t*,size_t,unsignedint);
ssize_t(*splice_read)(structfile*,loff_t*,structpipe_inode_info*,size_t,unsignedint);
int(*setlease)(structfile*,long,structfile_lock**,void**);
long(*fallocate)(structfile*file,intmode,loff_toffset,
loff_tlen);
void(*show_fdinfo)(structseq_file*m,structfile*f);
#ifndefCONFIG_MMU
unsigned(*mmap_capabilities)(structfile*);
#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 创建一个类:

externstructclass*__must_check__class_create(structmodule*owner,
constchar*name,
structlock_class_key*key);

#defineclass_create(owner,name)
({
staticstructlock_class_key__key;
__class_create(owner,name,&__key);
})

使用class_destroy 摧毁一个类:
extern void class_destroy(struct class *cls);

structclass{
constchar*name;
structmodule*owner;

structclass_attribute*class_attrs;
conststructattribute_group**dev_groups;
structkobject*dev_kobj;

int(*dev_uevent)(structdevice*dev,structkobj_uevent_env*env);
char*(*devnode)(structdevice*dev,umode_t*mode);

void(*class_release)(structclass*class);
void(*dev_release)(structdevice*dev);

int(*suspend)(structdevice*dev,pm_message_tstate);
int(*resume)(structdevice*dev);

conststructkobj_ns_type_operations*ns_type;
constvoid*(*namespace)(structdevice*dev);

conststructdev_pm_ops*pm;

structsubsys_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 将指针转化为数值返回。

staticinlinelong__must_checkPTR_ERR(constvoid*ptr)
{
	return(long)ptr;
}

staticinlinelong__must_checkIS_ERR(constvoid*ptr)
{
	returnIS_ERR_VALUE((unsignedlong)ptr);
}

代码示例

#include		//file_operations声明
#include//module_initmodule_exit声明
#include//__init__exit宏定义声明
#include	//classdevise声明
#include//copy_from_user的头文件
#include//设备号dev_t类型声明
#include//ioremapiounmap的头文件

#defineDEVICE_CNT0	//设备号个数

structled_device{
	dev_tdevid;	//设备号
	intmajor;	//主设备号
	intminor;	//次设备号
	char*name="led";	//驱动名
	structcdevled_dev;	//cdev结构体
	structclass*class;	/*类	*/
	structdevice*device;	//设备
};

structled_deviceled;


staticintled_open(structinode*inode,structfile*filp)
{
	return0;
}

staticssize_tled_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt)
{
	return0;
}


staticssize_tled_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt)
{
	return0;
}



/*设备操作函数*/
staticstructfile_operationsled_fo={
	.owner=THIS_MODULE,
	.open=led_open,
	.read=led_read,
	.write=led_write,
};

staticint_initled_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("newcheledmajor=%d,minor=%drn",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);	//创建设备

	return0;
		
}

staticvoid_exitled_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("GPLv2");
MODULE_AUTHOR("zhoujianghong");

应用open到file_operations->open 的调用原理到此,关于“linux下有哪些字符设备”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注云技术网站,小编会继续努力为大家带来更多实用的文章!

相关推荐: windows picpick如何设置中文

这篇文章主要介绍“windows picpick如何设置中文”,在日常操作中,相信很多人在windows picpick如何设置中文问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”windows picpick如何设置中文”的疑惑…

文章页内容下
赞(0) 打赏
版权声明:本站采用知识共享、学习交流,不允许用于商业用途;文章由发布者自行承担一切责任,与本站无关。
文章页正文下
文章页评论上

云服务器、web空间可免费试用

宝塔面板主机、支持php,mysql等,SSL部署;安全高速企业专供99.999%稳定,另有高防主机、不限制内容等类型,具体可咨询QQ:360163164,Tel同微信:18905205712

主机选购导航云服务器试用

登录

找回密码

注册