精华内容
下载资源
问答
  • 字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备常见字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l/dev的时候,就能...
  • 字符设备基础

    2016-02-15 15:42:27
    linux系统将设备分为3类:字符设备、块设备、网络设备 ...字符设备是面向流的设备,常见字符设备有鼠标、键盘、串口、控制台和LED设备等。  每一个字符设备或块设备都在/dev目录下对应一个设备文件。

    linux系统将设备分为3类:字符设备、块设备、网络设备

    字符设备:

          是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。

           每一个字符设备或块设备都在/dev目录下对应一个设备文件。


    展开全文
  • 字符设备、块设备、网络设备

    千次阅读 2017-08-25 10:48:33
    字符设备、块设备、网络设备设备模型设备驱动的代码量占内核程序的50%设备模型的意义: 为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作Driver ...

    字符设备、块设备、网络设备

    这里写图片描述

    设备模型

    设备驱动的代码量占内核程序的50%

    设备模型的意义:
    为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作Driver Model)的概念。设备模型将硬件设备归纳、分类,然后抽象出一套标准的数据结构和接口。驱动的开发,就简化为对内核所规定的数据结构的填充和实现。

    因为硬件设备多种多样,使得设备驱动程序繁多,设备模型将硬件设备分类,抽象出一套标准的数据结构和接口。

    一、字符设备

    1.特点

    一个字节一个字节读写的设备,
    读取数据需要按照先后数据(顺序读取
    常见的字符设备有鼠标、键盘、串口、控制台和LED设备
    每个字符设备在/dev目录下对应一个设备文件,linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备。

    2.上层应用如何调用底层驱动?

    1.应用层的程序open(“/dev/xxx”,mode,flags)打开设备文件,进入内核中,即虚拟文件系统中。
    2.VFS层的设备文件有对应的struct inode,其中包含该设备对应的设备号,设备类型,返回的设备的结构体。
    3.在驱动层中,根据设备类型和设备号就可以找到对应的设备驱动的结构体,用i_cdev保存。该结构体中有很重要的一个操作函数接口file_operations。
    4.在打开设备文件时,会分配一个struct file,将操作函数接口的地址保存在该结构体中。
    5.VFS层 向应用层返回一个fd,fd是和struct file相对应,这样,应用层可以通过fd调用操作函数,即通过驱动层调用硬件设备了。

    这里写图片描述
    这里写图片描述

    二、块设备

    1.特点

    数据以固定长度进行传输,比如512K
    从设备的任意位置(可跳)读取,但实际上,块设备会读一定长度的内容,而只返回用户要求访问的内容,所以随机访问实际上还是读了全部内容。
    块设备包括硬盘、磁盘、U盘和SD卡
    每个块设备在/dev目录下对应一个设备文件,linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作块设备。
    块设备可以容纳文件系统,比如磁盘

    三、网络设备

    1.特点

    面向报文而不是面向流的,因此将网络接口映射到文件系统的节点比较困难
    内核调用一套和数据包相关的函数,而不是read,write。
    网络接口没有像字符设备和块设备一样的设备号,只有唯一的名字,如eth0,eth1
    主要通过socket操作,打开通常用命令行,

    2.关系

    网络协议接口层:网络层,IP
    网络设备接口层:将协议和各种网络驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动使用。
    网络驱动接口层:数据链路层,提供对物理层访问的设备驱动程序,这可以是各种介质,例如串口链路或以太网设备。包括LLC和MAC层
    物理层:PHY层
    这里写图片描述

    展开全文
  • 字符设备驱动实验

    千次阅读 2019-06-11 08:38:54
    字符设备是面向流的设备,常见字符设备有鼠标、键盘、串口、控制台和LED等。 一般每个字符设备或者块设备都会在/dev目录(可以是任意目录,这样是为了统一)下对应一个设备文件。linux用户层程序通过设备文件来...

    一、字符设备基础

    字符设备:是指只能一个字节一个字节进行读写操作的设备,不能随机读取设备中的某一数据、读取数据要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED等。

    一般每个字符设备或者块设备都会在/dev目录(可以是任意目录,这样是为了统一)下对应一个设备文件。linux用户层程序通过设备文件来使用驱动程序操作字符设备或块设备

    二、驱动设备基础

    驱动程序

    驱动程序的作用是应用程序与硬件之间的一个中间软件层,驱动程序应该为应用程序展现硬件的所有功能,不应该强加其他的约束,对于硬件使用的权限和限制应该由应用程序层控制

    Linux 的驱动开发调试有两种方法,一种是直接编译到内核,再运行新的内核来测试;二是编译为模块的形式,单独加载运行调试。第一种方法效率较低,但在某些场合是唯一的方法。模块方式调试效率很高,它使用 insmod 工具将编译的模块直接插入内核,如果出现故障,可以使用rmmod从内核中卸载模块,不需要重新启动内核,这使驱动调试效率大大提高

     

    Linux内核简介

    Linux内核是一个整体是结构.因此向内核添加任何东西.或者删除某些功能,都十分困难。为了解决这个问题,引入了内核机制,从而可以可以动态的想内核中添加或者删除模块

    Linux的内核模块机制允许开发者动态的向内核添加功能,我们常见的文件系统、驱动程序等都可以通过模块的方式添加到内核而无需对内核重新编译,这在很大程度上减少了操作的复杂度。模块机制使内核预编译时不必包含很多无关功能,把内核做到最精简,后期可以根据需要进行添加。而针对驱动程序,因为涉及到具体的硬件,很难使通用的,且其中可能包含了各个厂商的私密接口,厂商几乎不会允许开发者把源代码公开,这就和Linux的许可相悖,模块机制很好的解决了这个冲突,允许驱动程序后期进行添加而不合并到内核

     

    驱动程序与应用程序的区别

    应用程序一般有一个main函数,从头到尾执行一个任务。驱动程序却不同,它没有 main 函数,通过使用宏module_init(初始化函数名),将初始化函数加入内核全局初始化函数列表中,在内核初始化时执行驱动的初始化函数,从而完成驱动的初始化和注册,之后驱动便停止等待被应用软件调用。驱动程序中有一个宏 moudule_exit(退出处理函数名)注册退出处理函数。它在驱动退出时被调用

    应用程序可以和 GLIBC 库连接,因此可以包含标准的头文件,比如 ,在驱动程序中是不能使用标准C库的,因此不能调用所有的C库函数,比如输出打印函数只能使用内核的 printk函数,包含的头文件只能是内核的头文件

     

    设备的分类

    以 Linux 的方式看待设备可区分为3种基本设备类型: 字符设备、块设备、网络设备

     

    字符设备: 一个字符(char)设备是一种可以当作一个字节流来存取的设备(如同一个文件); 一个字符驱动负责实现这种行为。这样的驱动常常至少实现open, close, read, 和 write 系统调用. 文本控制台(/dev/console)和串口(/dev/ttyS0及其它)是字符设备的例子, 因为它们很好地展现了流的抽象。字符设备通过文件系统结点来存取, 例如/dev/tty1和/dev/lp0。在一个字符设备和一个普通文件之间唯一有关的不同就是,你经常可以在普通文件中移来移去, 但是大部分字符设备仅仅是数据通道, 你只能顺序存取

     

    块设备: 如同字符设备, 块设备通过位于 /dev 目录的文件系统结点来存取。 一个块设备(例如一个磁盘)应该是可以驻有一个文件系统的。在大部分的 Unix 系统, 一个块设备只能处理这样的 I/O 操作,传送一个或多个长度经常是 512 字节( 或一个更大的2的幂的数 )的整块。Linux则相反, 允许应用程序读写一个块设备象一个字符设备一样---它允许一次传送任意数目的字节。结果就是,块和字符设备的区别仅仅在内核在内部管理数据的方式上, 并且因此在内核/驱动的软件接口上不同。如同一个字符设备, 每个块设备都通过一个文件系统结点被存取的, 它们之间的区别对用户是透明的。块驱动和字符驱动相比, 与内核的接口完全不同

     

    网络接口: 任何网络事务都通过一个接口来进行,就是说,一个能够与其他主机交换数据的设备。通常,一个接口是一个硬件设备,但是它也可能是一个纯粹的软件设备,比如环回接口。一个网络接口负责发送和接收数据报文,在内核网络子系统的驱动下,不必知道单个事务是如何映射到实际的被发送的报文上的。很多网络连接(特别那些使用TCP的)是面向流的, 但是网络设备却常常设计成处理报文的发送和接收。一个网络驱动对单个连接一无所知,它只处理报文

     

    Linux下的大部分驱动,是以模块形式进行编写的。这些驱动程序源码可以修改到内核中,也可以将它们编译成模块形式,在需要的时候动态加载

     

    字符设备是3大类设备中较简单的一类设备,其驱动程序中完成的主要工作是初始化、添加和删除struct cdev 结构体,申请和释放设备号,以及填充 struct file_operations结构体中断的操作函数,实现 struct file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作

     

    驱动程序实现框架

                        

    主要功能函数列表

      

    三、试验步骤

    1.linux源码及相关软件下载

    在进行字符驱动设备编写之前,需要先下载linux内核源码,并且下载相应软件以支持对linux源码的操作

    下载linux-3.0.15,并将文件解压到指定目录

     

    在试验之前,请确保你的内核源码包起码编译过一次,因为编译驱动时候需要用到内核源码的相关头文件及编译内核时所生成的过程文件,否则即便内核目录指定正确,也会报错。

     

    进入内核目录(必须是内核目录)之后,终端输入:

    make -j8(创建8个进程对内核进行编译,提高编译效率)

     

    进入解压生成的目录,终端输入:make menocofig对内核源码进行编辑

    执行结果如下图所示

     

    1. 创建试验目录:/nfsroot/kernel/wang

    终端输入:mkdir /nfsroot/kernel/wang创建试验目录

    使用vim编写源程序以及相关文件,详细内容见附件

    注意:

    Makefile中的:
    #KERNELDIR =
    #CROSS_COMPILE=
    
    两行宏变量定义用于指定编译器和内核目录,其中内核目录一定要与用户自己内核路径一致,这里使用的是出厂默
    认宿主机安装路径/UP-CUP4412/SRC/kernel/linux-3.0.15。编译器使用arm-none-linux-gnueabi-gcc并
    且保证你的内核源码包起码编译过一次,因为编译驱动时候需要用到内核源码的相关头文件及编译内核时所生成的
    过程文件,否则即便内核目录指定正确,也会报错

    3.进入试验目录,编译源程序

    使用make命令编译文件

    当前目录下生成驱动程序 demo.ko 和应用测试程序 test_demo

    4.nfs挂载试验目录

    启动移动互联网教研平台,连接好网线和串口线,使用X-shell连接宿主机和开发板之后,在开发板的终端输入:ifconfig eth0 192.168.1.199(根据个人情况)

       

       

     

    终端输入:mountnfs 192.168.1.112:/nfsroot /mnt/nfs

        

    将宿主机的/nfsroot目录挂载到开发板的/mnt/nfs目录,之后进入/mnt/nfs查看,发现成功挂载

        

    1. 手动加载驱动程序

    进入开发板终端,输入insmod demo.ko

       

    6、通过设备号建立驱动设备节点

    使用 cat /proc/devices 查看设备号

       

    查看找demo.ko驱动设备号,这里显示为 250,在 UP-MobNet-II 型网关部分系统/dev 目录下手动建立设备节点demo

    终端输入:mknod /dev/demo c 250 0

    c:表示 char 型设备 250: 表示主设备号 0:表示次设备号

     

    7查看下自己建立的设备 demo 属性

    终端输入:ll /dev/demo

    执行测试程序test_demo测试驱动及设备,执行结果如下:

     

       

    试验成功!!!

     

    demo.c源码如下

    #ifdef MODULE
    #include <linux/module.h>
    
    #ifdef CONFIG_DEVFS_FS
    #include <linux/devfs_fs_kernel.h>
    #endif
    
    
    #include <linux/init.h>
    #include <linux/kernel.h>   /* printk() */
    #include <linux/slab.h>   /* kmalloc() */
    #include <linux/fs.h>       /* everything... */
    #include <linux/errno.h>    /* error codes */
    #include <linux/types.h>    /* size_t */
    #include <linux/proc_fs.h>
    #include <linux/fcntl.h>    /* O_ACCMODE */
    #include <linux/poll.h>    /* COPY_TO_USER */
    #include <linux/uaccess.h>
    #include <asm/system.h>     /* cli(), *_flags */
    
    #define DEVICE_NAME	"UP-TECH DEMO"
    #define DEMORAW_MINOR	1
    #define DEMO_Devfs_path	"demo/0"
    
    static int demoMajor = 0;
    static int MAX_BUF_LEN=1024;
    static char drv_buf[1024];
    static int length=0;
    
    /***********************************************************************************/
    
    static void do_write(void)
    {
    
    	int i;
    	int len = length;
    	int mid = len>>1;
    	char tmp;
    	for(i = 0; i < mid; i++,len--){
    		tmp = drv_buf[len-1];
    		drv_buf[len-1] = drv_buf[i];
    		drv_buf[i] = tmp;
    	}
    }
    /***********************************************************************************/
    static ssize_t  demo_write(struct file *file, const char __user *buffer, size_t count, loff_t * ppos)
    { 
    	if(count > MAX_BUF_LEN)
    		length = MAX_BUF_LEN;
    	length = copy_from_user(drv_buf , buffer, count);
    	do_write();	
    	
    	return length;
    }
    /***********************************************************************************/
    static ssize_t  demo_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
    {
    	if(count > MAX_BUF_LEN)
    		length = MAX_BUF_LEN;
    	length = copy_to_user(buffer, drv_buf,count);
    	
    	return length;
    }
    /***********************************************************************************/
    static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
    	switch(cmd){
    		case 1:printk("runing command 1 \n");break;
    		case 2:printk("runing command 2 \n");break;
    		default:
    			printk("error cmd number\n");break;
    	}
    	return 0;
    }
    /***********************************************************************************/
    static int demo_open(struct inode *inode, struct file *filp)
    {
    	printk(KERN_DEBUG" device open sucess!\n");
    	return 0;
    }
    /***********************************************************************************/
    static int  demo_release(struct inode *inode, struct file *filp)
    {
    //	MOD_DEC_USE_COUNT;
    	printk(KERN_DEBUG "device release\n");
    	return 0;
    }
    
    /***********************************************************************************/
    static struct file_operations pxa270_fops = {
    	owner:	THIS_MODULE,
    	write:	demo_write,	
    	read:	demo_read,	
    	unlocked_ioctl:	demo_ioctl,
    	open:	demo_open,
    	release:	demo_release,
    };
    /***********************************************************************************/
    
    static int __init demo_init(void)
    {
     int ret;
     	ret = register_chrdev(0, DEVICE_NAME, &pxa270_fops);
    	if (ret < 0) {
    		printk(DEVICE_NAME " can't get major number\n");
    		return ret;
    	}
    	demoMajor=ret;
    	printk("demo driver initialized \n");
    	#ifdef CONFIG_DEVFS_FS
    	devfs_mk_cdev(MKDEV(demoMajor, DEMORAW_MINOR), 
    			S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, DEMO_Devfs_path);
    	#endif
    	return 0;
    }
    
    /***********************************************************************************/
    #ifdef MODULE
    void __exit  demo_exit(void)
    {
    #ifdef CONFIG_DEVFS_FS	
    	devfs_remove(DEMO_Devfs_path);
    #endif
    	unregister_chrdev(demoMajor, DEVICE_NAME);
    }
    module_exit(demo_exit);
    #endif
    
    /***********************************************************************************/
    module_init(demo_init);
    
    MODULE_LICENSE("Dual BSD/GPL");
    #endif		// MODULE
    

     

     

    test_demo.c源码如下

    #ifdef MODULE
    #include <linux/module.h>
    
    #ifdef CONFIG_DEVFS_FS
    #include <linux/devfs_fs_kernel.h>
    #endif
    
    
    #include <linux/init.h>
    #include <linux/kernel.h>   /* printk() */
    #include <linux/slab.h>   /* kmalloc() */
    #include <linux/fs.h>       /* everything... */
    #include <linux/errno.h>    /* error codes */
    #include <linux/types.h>    /* size_t */
    #include <linux/proc_fs.h>
    #include <linux/fcntl.h>    /* O_ACCMODE */
    #include <linux/poll.h>    /* COPY_TO_USER */
    #include <linux/uaccess.h>
    #include <asm/system.h>     /* cli(), *_flags */
    
    #define DEVICE_NAME	"UP-TECH DEMO"
    #define DEMORAW_MINOR	1
    #define DEMO_Devfs_path	"demo/0"
    
    static int demoMajor = 0;
    static int MAX_BUF_LEN=1024;
    static char drv_buf[1024];
    static int length=0;
    
    /***********************************************************************************/
    
    static void do_write(void)
    {
    
    	int i;
    	int len = length;
    	int mid = len>>1;
    	char tmp;
    	for(i = 0; i < mid; i++,len--){
    		tmp = drv_buf[len-1];
    		drv_buf[len-1] = drv_buf[i];
    		drv_buf[i] = tmp;
    	}
    }
    /***********************************************************************************/
    static ssize_t  demo_write(struct file *file, const char __user *buffer, size_t count, loff_t * ppos)
    { 
    	if(count > MAX_BUF_LEN)
    		length = MAX_BUF_LEN;
    	length = copy_from_user(drv_buf , buffer, count);
    	do_write();	
    	
    	return length;
    }
    /***********************************************************************************/
    static ssize_t  demo_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
    {
    	if(count > MAX_BUF_LEN)
    		length = MAX_BUF_LEN;
    	length = copy_to_user(buffer, drv_buf,count);
    	
    	return length;
    }
    /***********************************************************************************/
    static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
    	switch(cmd){
    		case 1:printk("runing command 1 \n");break;
    		case 2:printk("runing command 2 \n");break;
    		default:
    			printk("error cmd number\n");break;
    	}
    	return 0;
    }
    /***********************************************************************************/
    static int demo_open(struct inode *inode, struct file *filp)
    {
    	printk(KERN_DEBUG" device open sucess!\n");
    	return 0;
    }
    /***********************************************************************************/
    static int  demo_release(struct inode *inode, struct file *filp)
    {
    //	MOD_DEC_USE_COUNT;
    	printk(KERN_DEBUG "device release\n");
    	return 0;
    }
    
    /***********************************************************************************/
    static struct file_operations pxa270_fops = {
    	owner:	THIS_MODULE,
    	write:	demo_write,	
    	read:	demo_read,	
    	unlocked_ioctl:	demo_ioctl,
    	open:	demo_open,
    	release:	demo_release,
    };
    /***********************************************************************************/
    
    static int __init demo_init(void)
    {
     int ret;
     	ret = register_chrdev(0, DEVICE_NAME, &pxa270_fops);
    	if (ret < 0) {
    		printk(DEVICE_NAME " can't get major number\n");
    		return ret;
    	}
    	demoMajor=ret;
    	printk("demo driver initialized \n");
    	#ifdef CONFIG_DEVFS_FS
    	devfs_mk_cdev(MKDEV(demoMajor, DEMORAW_MINOR), 
    			S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, DEMO_Devfs_path);
    	#endif
    	return 0;
    }
    
    /***********************************************************************************/
    #ifdef MODULE
    void __exit  demo_exit(void)
    {
    #ifdef CONFIG_DEVFS_FS	
    	devfs_remove(DEMO_Devfs_path);
    #endif
    	unregister_chrdev(demoMajor, DEVICE_NAME);
    }
    module_exit(demo_exit);
    #endif
    
    /***********************************************************************************/
    module_init(demo_init);
    
    MODULE_LICENSE("Dual BSD/GPL");
    #endif		// MODULE
    root@ubuntu:/nfsroot/kernel/wang# cat test_demo.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    
    
    void showbuf(char *buf);
    int MAX_LEN=32;
    
    int main()
    {
    	int fd;
    	int i;
    	char buf[255];
    	
    	for(i=0; i<MAX_LEN; i++){
    		buf[i]=i;
    	}
    
    	fd=open("/dev/demo",O_RDWR);
    	if(fd < 0){
    		printf("####DEMO  device open fail####\n");
    		return (-1);
    	}
    	printf("write %d bytes data to /dev/demo \n",MAX_LEN);
    	showbuf(buf);
    	write(fd,buf,MAX_LEN);
    
    	printf("Read %d bytes data from /dev/demo \n",MAX_LEN);
    	read(fd,buf,MAX_LEN);
    	showbuf(buf);
    	
    	close(fd);
    	return 0;
    
    }
    
    
    void showbuf(char *buf)
    {
    	int i,j=0;
    	for(i=0;i<MAX_LEN;i++){
    		if(i%4 ==0)
    			printf("\n%4d: ",j++);
    		printf("%4d ",buf[i]);
    	}
    	printf("\n*****************************************************\n");
    }
    

     

     

     

    modules.order内容如下

    kernel//nfsroot/kernel/wang/demo.ko

     

    Makefile内容如下

    # To build modules outside of the kernel tree, we run "make"
    # in the kernel source tree; the Makefile these then includes this
    # Makefile once again.
    # This conditional selects whether we are being included from the
    # kernel Makefile or not.
    
    TARGET = test_demo
    CROSS_COMPILE = arm-none-linux-gnueabi-
    
    CC              = $(CROSS_COMPILE)gcc
    STRIP           = $(CROSS_COMPILE)strip
    #CFLAGS          = -O2
    
    
    ifeq ($(KERNELRELEASE),)
    
        # Assume the source tree is where the running kernel was built
        # You should set KERNELDIR in the environment if it's elsewhere
    #     KERNELDIR ?=/home/sprife/kernel/linux-2.6.24.4 
    KERNELDIR ?=/nfsroot/kernel/linux-3.0.15
    #   KERNELDIR ?= /usr/src/kernels/2.6.9-42.EL-smp-i686/
        # The current directory is passed to sub-makes as argument
        PWD := $(shell pwd)
    
    all:	$(TARGET) modules
    
    $(TARGET):
    	$(CC) -o $(TARGET) $(TARGET).c
    
    modules:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    
    modules_install:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
    
    clean:
    	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions $(TARGET)
    
    .PHONY: modules modules_install clean
    
    else
        # called from kernel build system: just declare what our modules are
        obj-m :=  demo.o
    
    endif
    

     

    Module.symvers为空文件

    展开全文
  • Linux一切皆文件,系统与设备通信之前,要建立一个存放在/dev目录下的设备文件,默认...创建一个名为”linuxcool”的字符设备: [root@linuxcool ~]# mknod linuxcool c 30 1 创建一个名为”linuxcool”的块设备: [roo
  • 字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备是字节流形式通讯的I/O设备,绝大部分设备都是字符设备常见字符设备包括鼠标、键盘、显示器、串口等等,当我们执行 ls -l /dev 的时候,能...
  • 字符设备驱动

    千次阅读 2018-07-16 14:52:55
    一、字符设备基础&nbsp; 字符设备 二、字符设备驱动与用户空间访问该设备的程序三者之间的关系 三、字符设备模型 1、Linux内核中,使用 struct cdev 来描述一个字符设备 动态申请(构造)cdev内存(设备...

     

    一、字符设备基础

    字符设备:是指只能一个字节一个字节进行读写操作的设备,不能随机读取设备中的某一数据、读取数据要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED等。

    一般每个字符设备或者块设备都会在/dev目录(可以是任意目录,这样是为了统一)下对应一个设备文件。linux用户层程序通过设备文件来使用驱动程序操作字符设备或块设备。

    二、字符设备驱动与用户空间访问该设备的程序三者之间的关系

    字符设备是3大类设备(字符设备、块设备、网络设备)中较简单的一类设备、其驱动程序中完成的主要工作是初始化、添加和删除 struct cdev 结构体,申请和释放设备号,以及填充 struct file_operations 结构体中断的操作函数,实现 struct file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。

     

    如图,在Linux内核代码中:

    • 使用struct cdev结构体来抽象一个字符设备;
    • 通过一个dev_t类型的设备号(分为主(major)、次设备号(minor))一确定字符设备唯一性;
    • 通过struct file_operations类型的操作方法集来定义字符设备提供个VFS的接口函数。 

     

     三、字符设备模型

     

     

    1、Linux内核中,使用 struct cdev 来描述一个字符设备

    复制代码
    <include/linux/cdev.h>  
    
    struct cdev {   
      struct kobject kobj;                  //内嵌的内核对象.  
      struct module *owner;                 //该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULE主要用于模块计数  
      const struct file_operations *ops;    //该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、...),是极为关键的一个结构体
      struct list_head list;                //用来将已经向内核注册的所有字符设备形成链表
      dev_t dev;                            //字符设备的设备号,由主设备号和次设备号构成(如果是一次申请多个设备号,此设备号为第一个)
      unsigned int count;                   //隶属于同一主设备号的次设备号的个数
      ...
    };  
    复制代码

     对于struct cdev内核提供了一些操作接口:

     头文件linux/cdev.h

    动态申请(构造)cdev内存(设备对象)

    struct cdev *cdev_alloc(void);  
    /* 返回值:
        成功 cdev 对象首地址
        失败:NULL */

    初始化cdev的成员,并建立cdev和file_operations之间关联起来 

    void cdev_init(struct cdev *p, const struct file_operations *p);  
    /* 参数:
        struct cdev *p - 被初始化的 cdev对象
        const struct file_operations *fops - 字符设备操作方法集 */

    注册cdev设备对象(添加到系统字符设备列表中)

    复制代码
    int cdev_add(struct cdev *p, dev_t dev, unsigned count);
    /* 参数:
        struct cdev *p - 被注册的cdev对象
        dev_t dev - 设备的第一个设备号
        unsigned - 这个设备连续的次设备号数量
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/
    复制代码

    将cdev对象从系统中移除(注销 )

    void cdev_del(struct cdev *p);
    /*参数: 
        struct cdev *p - 要移除的cdev对象 */

    释放cdev内存

    void cdev_put(struct cdev *p);
    /*参数:
        struct cdev *p - 要移除的cdev对象 */

     

    2、设备号申请/释放

    一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

    linux内核中,设备号用dev_t来描述:

    typedef u_long dev_t;  // 在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。 

    内核也为我们提供了几个方便操作的宏实现dev_t: 

    #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))  // 从设备号中提取主设备号
    #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))  // 从设备号中提取次设备号
    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))</span>  // 将主、次设备号拼凑为设备号
    /* 只是拼凑设备号,并未注册到系统中,若要使用需要竞态申请 */

    头文件 linux/fs.h 

    a - 静态申请设备号

    复制代码
    int register_chrdev_region(dev_t from, unsigned count, const char *name);
    /* 参数:
        dev_t from - 要申请的设备号(起始)
        unsigned count - 要申请的设备号数量
        const char *name - 设备名
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/
    复制代码

    b - 动态分配设备号

    复制代码
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
    /* 参数:
        dev_t *dev - 用于保存分配到的第一个设备号(起始)
        unsigned baseminor - 起始次设备号
        unsigned count - 要分配设备号的数量
        const char *name - 设备名
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/
    复制代码

    c - 释放设备号

    void unregister_chrdev_region(dev_t from, unsigned count);
    /* 参数:
        dev_t from - 要释放的第一个设备号(起始)
        unsigned count - 要释放的次设备号数量 */

     

    d、创建设备文件:

    利用cat /proc/devices查看申请到的设备名,设备号。

    1. 使用mknod手工创建:mknod filename type major minor
    2. 自动创建设备节点:利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

    详细解析见: Linux设备文件自动生成

     

    3、struct cdev 中的 file_operations *fops成员

    Linux下一切皆是“文件”,字符设备也是这样,file_operations结构体中的成员函数是字符设备程序设计的主题内容,这些函数实际会在用户层程序进行Linux的open()、close()、write()、read()等系统调用时最终被调用。

    标准化:如果做到极致,应用层仅仅需要一套系统调用接口函数。

    “文件”的操作接口结构:

    复制代码
    struct file_operations {
      struct module *owner;  
        /* 模块拥有者,一般为 THIS——MODULE */   ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
        /* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */   ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);   
        /* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/   int (*mmap) (struct file *, struct vm_area_struct *);  
        /* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */   long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);  
        /* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */   int (*open) (struct inode *, struct file *);  
        /* 打开设备 */   int (*release) (struct inode *, struct file *);  
        /* 关闭设备 */   int (*flush) (struct file *, fl_owner_t id);  
        /* 刷新设备 */   loff_t (*llseek) (struct file *, loff_t, int);  
        /* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */   int (*fasync) (int, struct file *, int);  
        /* 通知设备 FASYNC 标志发生变化 */   unsigned int (*poll) (struct file *, struct poll_table_struct *);  
        /* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */   ... };
    复制代码

     四、简单字符设备实例

    cdev_module.c

    复制代码
    #include <linux/init.h>
    
    
    
    
    
    #include <linux/module.h> #include <linux/kernel.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/errno.h> #include <asm/current.h> #include <linux/sched.h>
    MODULE_LICENSE("GPL");
    static int major = 0; 
    static int minor = 0;
    const int count = 3;
    #define DEVNAME "demo"
    static struct cdev *demop = NULL;
    //打开设备
    static int demo_open(struct inode *inode, struct file *filp)
    {
      //get command and pid
      printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
      //get major and minor from inode
      printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
      return 0;
    }
    //关闭设备
    static int demo_release(struct inode *inode, struct file *filp)
    {
      //get command and pid
      printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
      //get major and minor from inode
      printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
      return 0;
    }
    //读设备
    static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
    {
      struct inode *inode = filp->f_path.dentry->d_inode;
      //get command and pid
      printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
      //get major and minor from inode
      printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
      return 0;
    }
    //写设备
    static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
    {
      struct inode *inode = filp->f_path.dentry->d_inode;
      //get command and pid
      printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
      //get major and minor from inode
      printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n", imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
      return 0;
    }
    //操作方法集
    static struct file_operations fops = {
      .owner = THIS_MODULE, .open = demo_open,
      .release= demo_release,
      .read = demo_read,
      .write = demo_write,
    };
    //cdev设备模块初始化
    static int __init demo_init(void)
    {
      dev_t devnum; int ret;
      //get command and pid
      printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n", current->comm, current->pid, __FILE__, __func__, __LINE__);
      //1. alloc cdev obj
      demop = cdev_alloc();
      if(NULL == demop) {
        return -ENOMEM;
      } //2. init cdev obj cdev_init(demop, &fops);
    ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME); if(ret){ goto ERR_STEP; } major = MAJOR(devnum); //3. register cdev obj ret = cdev_add(demop, devnum, count); if(ret){ goto ERR_STEP1; } //get command and pid printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n", current->comm, current->pid, __FILE__, __func__, __LINE__); return 0; ERR_STEP1: unregister_chrdev_region(devnum, count); ERR_STEP: cdev_del(demop); //get command and pid printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n", current->comm, current->pid, __FILE__, __func__, __LINE__); return ret; } static void __exit demo_exit(void) { //get command and pid printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n", current->comm, current->pid, __FILE__, __func__, __LINE__); unregister_chrdev_region(MKDEV(major, minor), count); cdev_del(demop); } module_init(demo_init); module_exit(demo_exit);
    复制代码

     test.c

    复制代码
    #include <stdio.h>
    
    
    
    
    
    #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main( int num, char * arg[]) { if( 2 != num){ printf( ” Usage: %s /dev/devfile\n ”, arg[ 0 ]); return - 1 ; } int fd = open(arg[ 1 ], O_RDWR); if( 0 > fd){ perror( ” open ” ); return - 1 ; } getchar(); int ret = read(fd, 0x321, 0 ); printf( ” read: ret = %d.\n ” , ret); getchar(); ret = write(fd, 0x123, 0 ); printf( ” write: ret = %d.\n ” , ret); getchar(); close(fd); return 0 ; }
    复制代码

    Makefile 

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ifneq ($(KERNELRELEASE),)</code></div><div class="line number2 index1 alt1"><code class="bash spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="bash plain">obj-m = demo.o</code></div><div class="line number3 index2 alt2"><code class="bash keyword">else</code></div><div class="line number4 index3 alt1"><code class="bash spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="bash plain">KERNELDIR :=&nbsp; </code><code class="bash plain">/lib/modules/</code><code class="bash plain">$(shell uname -r) /build
         PWD       := $(shell </code><code class="bash functions">pwd</code><code class="bash plain">)</code></div><div class="line number6 index5 alt1"><code class="bash plain">modules:</code></div><div class="line number7 index6 alt2"><code class="bash spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="bash plain">$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    endif
     
    clean:
         rm -rf .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c

    编译成功后,使用 insmod 命令加载:

    然后用cat /proc/devices 查看,会发现设备号已经申请成功;

    转: https://www.cnblogs.com/chen-farsight/p/6155518.html#unit1.1

    展开全文
  • 块设备 字符设备

    2010-09-26 11:34:00
    块设备 字符设备
  • 字符设备驱动模板

    2012-11-22 00:49:20
    常用字符设备的一个模板,往里面填充相应内容即可使用。
  • 值得注意的是,内核为了简化某些常用字符设备驱动的设备,也会提供一个中间层作为缓冲,如 TTY 中间层。用户可以自行选择是否使用这些中间层。一般而言,对于UART设备,建议读者使用内核提供的 TTY 中间层,以提高...
  • Linux字符设备驱动

    千次阅读 2017-08-27 18:36:35
    1. Linux设备类型Linux内核中的设备可分为三类:字符设备、块设备和网络设备。 字符设备(Character device):适合面向字符的数据交换,因其数据传输量较低。对这种设备的读写是按字符进行的,而且这些字符是连续...
  • 字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备常见字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l /dev的时候,就能...
  • Linux 字符设备记录

    2013-03-25 23:05:06
    常见字符设备:字符终端(/dev/console),串口(/dev/ttyS0以及类似设备)等,字符设备可以通过文件系统节点来访问,这些设备文件和普通文件之间的唯一差别在于普通文件的访问可以前后移动访问位置,而大多数字符...
  • 字符设备,块设备,网络设备。//这篇日志说的是字符设备驱动简单介绍。   块设备与字符设备粗略的区别在于 :  符设备是只能一个字节一个字节读写,不能随机读取设备内存中的某一数据。 常见的有 鼠标,键盘...
  • 关于块设备 和 字符设备 介绍:                 系统中能够随机(不需要按顺序)访问固定大小数据片(chunks)的设备被称作块设备,这些数据片就称作块。     &...
  • 字符设备、块设备、网络设备 设备模型 设备驱动的代码量占内核程序的50% 设备模型的意义: 为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作...
  • 字符设备与块设备的区别

    千次阅读 多人点赞 2019-06-18 13:46:31
    字符设备与块设备的区别 字符设备与块设备的区别 在LINUX里面,设备类型分为:字符设备、块设备以及网络设备,PCI是一种和ISA为一类的总线结构,归属于网络驱动设备~~~ 字符设备、块设备主要区别是:在对字符设备...
  • Linux字符驱动设备

    2019-10-06 17:23:21
    字符设备是面向流的设备,常见字符设备有鼠标、键盘、串口、控制台和LED等。 一般每个字符设备或者块设备都会在/dev目录(可以是任意目录,这样是为了统一)下对应一个设备文件。linux用户层程序通过设备文件来...
  • 字符设备是面向流的设备,常见字符设备有鼠标、键盘、串口、控制台和LED设备等。块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。  每一个字符设备或块设备都
  • 一、字符设备基础知识 1、设备驱动分类  linux系统将设备分为3类:...字符设备是面向流的设备,常见字符设备有鼠标、键盘、串口、控制台和LED设备等。 块设备:是指可以从设备的任意位置读取一定长度数据的设备。块
  • Linux设备驱动之字符设备驱动

    万次阅读 多人点赞 2016-07-01 19:36:15
    一、linux系统将设备分为3类:字符设备、块设备、网络...字符设备是面向流的设备,常见字符设备如鼠标、键盘、串口、控制台、LED等。 块设备:是指可以从设备的任意位置读取一定长度的数据设备。块设备如硬盘、磁盘
  • Linux设备驱动之字符设备(一)

    千次阅读 2016-06-22 15:27:59
    字符设备:在传送过程中以字符为单位,一个字节一个字节的读写,不能随机的读写数据,因为这类设备读写速度比较缓慢(因而其内核设施中不提供缓存机制),常见字符设备有键盘,鼠标已打印机设备等。块设备: 是指...
  • 字符设备 块设备

    2011-12-16 10:10:58
    字符设备还是块设备的定义属于操作系统的设备访问层,与实际物理设备的特性无必然联系。 设备访问层下面是驱动程序,所以只要驱动程序提供的方式,都可以。也就是说驱动程序支持stream方式,那么就可以用这种方式...
  • 字符设备(裸设备)和块设备的区别

    千次阅读 2013-05-29 17:04:02
    Character Device Drive被称作字符设备或者裸设备(raw devices),Block Device Drive通常称为块设备。/dev/disk对应的为块设备,文件系统操作用到它,如mount ,/dev/rdisk对应的为字符设备(裸设备,rdi
  • 字符设备与块设备

    2012-09-27 19:05:21
    系统中能够随机(不需要按顺序)访问固定大小数据片(chunks)的设备被称作块设备,这些数据片就称作块。最常见的块设备是硬盘,除此以外,还有软盘...字符设备按照字符流的方式被有序访问,像串口和键盘就都属于字符
  • 字符设备是面向流的设备,常见字符设备有鼠标、键盘、串口、控制台和LED设备等。 块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。 每一个字符设备或块设备都在/dev...
  • Linux中块设备和字符设备的区别

    千次阅读 2014-09-06 09:21:19
    系统中能够随机(不需要按顺序)访问固定大小数据片(chunks)的设备被称作块设备,这些数据片就称作块。...字符设备按照字符流的方式被有序访问,像串口和键盘就都属于字符设备。如果一个硬件设备是以字
  • 块设备与字符设备

    2012-11-14 17:55:16
    I/O设备大致分为两类:块设备和字符设备。块设备将信息存储在固定大小的块中,每个块都有自己的地址。数据块的大小通常在512字节到32768字节之间。块设备的基本特征是每个块都能独立于其它块而读写。磁盘是最常见的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 286,069
精华内容 114,427
关键字:

常见的字符设备