字符设备基础

字符设备基础
字符设备基础

Linux 字符设备基础

字符设备驱动程序在系统中的位置

操作系统内核需要访问两类主要设备,简单的字符设备,如打印机,键盘等;块设备,如软盘、硬盘等。与此对应,有两类设备驱动程序。分别称为字符设备驱动程序和块设备驱动程序。两者的主要差异是:与字符设备有关的系统调用几乎直接和驱动程序的内部功能结合在一起。而读写块设备则主要和快速缓冲存储区打交道。只有需要完成实际的输入/输出时,才用到块设备驱动程序。见下图:

Linux 设备驱动程序的主要功能有:

● 对设备进行初始化;

● 使设备投入运行和退出服务;

● 从设备接收数据并将它们送到内核;

● 将数据从内核送到设备;

检测和处理设备出现的错误。

当引导系统时,内核调用每一个驱动程序的初始化函数。它的任务之一是将这一设备驱动程序使用的主设备号通知内核。同时,初始化函数还将驱动程序中的函数地址结构的指针送给内核。

内核中有两X 表。一X 表用于字符设备驱动程序,另一X 用于块设备驱动程序。这两X 表用来保存指向file_operations 结构的指针,

设备驱动程序内部的函数地址就保

存在这一结构中。内核用主设备号作为索引访问file_operations结构,因而能访问驱动程序内的子程序。

从开机到驱动程序的载入

系统启动过程中可能出现几种不同的方式检测设备硬件。首先机器硬件启动时BIOS会检测一部分必要的设备,如内存、显示器、键盘和硬盘等等。机器会把检测到的信息存放在特定的位置,如CMOS数据区。而另外某些设备会由设备驱动程序进行检测。

1 开机

2 引导部分(linux/config.h,arch/i386/boot/bootsect.S)

3 实模式下的系统初始化(arch/i386/boot/setup.S)

4 保护模式下的核心初始化

5 启动核心(init/main.c)

init函数中函数调用关系如下:

main.c init()

filesystems.c sys_setup()

genhd.c device_setup()

mem.c chr_dev_init()

至此,驱动程序驻入内存。

设备驱动程序基本数据结构:

struct device_struct

系统启动过程中要登记的块设备和字符设备管理表的定义在文件fs/devices.c中:struct device_struct {

const char * name;

struct file_operations * fops;

};

static struct device_struct chrdevs[MAX_CHRDEV];

static struct device_struct blkdevs[MAX_BLKDEV];

其实块设备表和字符设备表使用了相同的数据结构。在某些系统中,这些设备表也称作设备开关表,不同的是它们直接定义了一组函数指针进行对设备的管理。而这里系统用文件操作(file_operations)代替了那组开关。文件操作是文件系统与设备驱动程序之间的接口,系统特殊文件在建立的时候并没有把两者对应起来,只是把设备的缺省文件结构和i节点结构赋给设备文件,而真正的对应定义在系统启动之后,当设备被打开时时才进行的。

操作blkdev_open和chrdev_open定义在文件devices.c中,它们的基本功能是当设备文件初次打开时,根据该文件的i节点信息找到设备真正的文件操作接口,然后更新原来的设

备表项;最后再调用该设备的open操作。

/include/linux/major.h中定义了设备表的长度。设备表中不同的表项表示不同种类的设备,也就是说,LINUX系统分别支持各128种不同的块设备和字符设备。

Struct file_operations

操作系统将一个字符设备当作文件来处理,内核通过file_operations结构来访问driver 的功能。这也是linux的OO思想的体现之一。file_operations的定义在文件中。每个字符设备都有一个file_operatioins结构。这个结构指向一组操作函数(open,read…). 每个函数的定义由driver提供。当然,有些标准操作某些设备并不支持,这时,file_operatons 结构中对应表项为NULL

(.随着linux内核的不断升级,file_operatioins结构也不断变大。最新的版本中,甚至函数原型也发生了一些变化。当然,新版本总会向下兼容的。)

下面是2.0.35中的file_operations结构定义:

struct file_operations {

int (*lseek) (struct inode *, struct file *, off_t, int);

int (*read) (struct inode *, struct file *, char *, int);

int (*write) (struct inode *, struct file *, const char *, int);

int (*readdir) (struct inode *, struct file *, void *, filldir_t);

int (*select) (struct inode *, struct file *, int, select_table *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

int (*mmap) (struct inode *, struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

void (*release) (struct inode *, struct file *);

int (*fsync) (struct inode *, struct file *);

int (*fasync) (struct inode *, struct file *, int);

int (*check_media_change) (kdev_t dev);

int (*revalidate) (kdev_t dev);

};

Struct inode

file_operations中的大多数操作都将inode做为第一个参数。Linux的VFS是对物理文件系统,物理设备的一个封装。Inode结构就是VFS与下层模块对话的重要结构。文件系统由子目录和文件构成。每个子目录或文件只能由唯一的inode描述。每个设备也是用inode来描述的。inode 是LINUX管理文件系统的最基本单位,也是文件系统连接任何子目录、任何文件,设备的桥梁。

struct inode {

kdev_t i_dev; /* 文件所在设备的设备号,第一个IDE硬盘为0x0301 */ unsigned long i_ino; /* 外存inode的节点号,

(i_dev,i_ino)在VFS中是唯一的 */

umode_t i_mode;/* 表示文件类型以及存取权限 */

nlink_t i_nlink;/* 连接到该文件的link数 */

uid_t i_uid; /* 用户标识号 */

gid_t i_gid; /* 用户组标识号 */

kdev_t i_rdev;/* 根设备的设备号 */

off_t i_size;/* 文件长度 */

time_t i_atime;/* 文件访问时间 */

time_t i_mtime; /* 文件修改时间 */

time_t i_ctime; /* 文件创建时间 */

unsigned long i_blksize;/* 以字节为单位的块大小,一般为1024字节 */ unsigned long i_blocks; /* 文件块数 */

unsigned long i_version;

unsigned long i_nrpages;/* 文件在内存中所占页数 */

struct semaphore i_sem; /* 信号量 */

struct inode_operations *i_op;/* 指向一组针对该文件的操作函数,

见fs.h */

struct super_block *i_sb; /* 指向内存中VFS的超级块 */

struct wait_queue *i_wait; /* 在该文件上的等待队列 */

struct file_lock *i_flock;

struct vm_area_struct *i_mmap;

struct page *i_pages; /* 由文件占用页面构成的单向链,

通过它可访问内存中的文件数据 */ struct dquot *i_dquot[MAXQUOTAS];

struct inode *i_next, *i_prev; /* inode资源管理中使用的链表指针 */ struct inode *i_hash_next, *i_hash_prev; /* inodecache的链表指针 */ struct inode *i_bound_to, *i_bound_by;

struct inode *i_mount; /* 指向下挂文件系统的inode的根目录 */

unsigned long i_count; /* 引用记数,0表示是空闲inode */

unsigned short i_flags;

unsigned short i_writecount;

unsigned char i_lock; /* 对inode加锁标志 */

unsigned char i_dirt;

unsigned char i_pipe;

unsigned char i_sock;

unsigned char i_seek;

unsigned char i_update;

unsigned char i_condemned;

union { /* 各类文件系统inode的特定信息 */ ............

struct ext2_inode_info ext2_i;

............

} u;

};

对于设备管理而言,主要用到的是inode结构的kdev I_rdev字段。

Struct kdev_t

文件系统中,字符设备是通过名字来访问的。通常字符设备都在/dev下。

在系统内部,每个字符设备都用设备号来表示。设备号由主,副设备号来表示。

主设备号表示与设备对应的设备驱动程序。如:设备/dev/zero 和/dev/null

都用1作为主设备号,表示他们使用相同的设备驱动程序。

副设备号只供驱动程序使用。通常一个驱动程序控制几个设备,它通过副设备号来辨别他们。与设备号相关的数据结构为kdev_t

Kdev_t 结构主要用来表示一个设备的主设备号和副设备号。它其实只是一个短的非负整数:

typedef unsigned short kdev_t;

但是,我们可以把它看作:

typedef struct { unsigned short major, minor; } kdev_t;

内核提供了一组宏来操作kdev_t结构:

MAJOR(kdev_t dev) : extract the major number from a kdev_t structure

MINOR(kdev_t dev) : extract the minor number from a kdev_t structure

MKDEV( int ma, int mi):return a kdev_t built from major and minor numbers

旧版本的内核dev_t对应kdev_t

Struct file

中定义的file 结构是设备驱动程序中用到的仅次于file_operations的数据结构。这里的file结构和用户程序用到的FILE结构不同。它们定义在不同的空间。

一个file结构代表一个”打开的文件”,它在文件被打开时被创建,然后被传给要对文件进行操作的函数,知道文件被关闭。一个打开的文件和一个磁盘上的文件不同,磁盘文件用inode表示。

下面讨论一些重要的域:

mode_t f_mode;

文件的读写模式由f_mode的FMODE_READ和FMODE_WRITE控制。在ioctl函数中需要通过此位控制文件的读写权限。当然,read ,write函数不需要检查f_mode

因为读写权限的检查是由内核在调用他们之前进行的。

loff_t f_pos

当前的读写位置。Loff_t是一个64位的值。

unsigned short f_flags;

也是文件的一些标志。如:O_RDONL Y,O_NONBLOCK和O_SYNC.驱动程序必须检查f_flags 以进行非阻塞操作。读写操作控制必须用f_mode.

struct inode * f_inode;

所打开的文件对应的inode,

struct file_operations * f_op;

对打开的文件的处理函数。一般在打开文件或设备初始化时由内核赋值。可以动态的修改此结构来改变文件的处理函数。

void *private_data;

open 系统调用把此指针设为NULL,然后再调用驱动程序的open函数。驱动程序可以自由得使用,甚至忽略它。

struct file {

m ode_t f_mode;

l off_t f_pos;

u nsigned short f_flags;

u nsigned short f_count;

u nsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;

s truct file *f_next, *f_prev;

s truct fown_struct f_owner;

s truct inode * f_inode;

s truct file_operations * f_op;

u nsigned long f_version;

v oid *private_data; /* needed for tty driver, and maybe others */

};

驱动程序的安装

写完了设备驱动程序,下一项任务是对它进行编译和装入可引导的内核。实际上这并不复杂,可以用下面的步骤来完成:

●将drivers.c和任何有关的drivers.h文件复制包含字符设备驱动程序源码的目录下,它放

在Linux源程序目录下的drivers/char子目录中。

●在chr_dev_init()函数的最后增加调用init()子程序的行。chr_dev_init()函数在

drivers/char/mem.c中。

●编辑drivers/char目录中的makefile,将drivers.o的名称放在OBJS定义的后面,并将

drivers.c名称放在SRCS定义的后面。

●将重启动函数加到main.c中的bootsetups数组

●重新编译和安装新的内核。

另:

登记一个字符设备驱动程序:

int register_chrdev(unsigned int major , const char * name,struct file_operations * fops);

登记一个字符设备:

mknod /dev/scull0 c 127 0

字符设备驱动程序课程设计报告

中南大学 字符设备驱动程序 课程设计报告 姓名:王学彬 专业班级:信安1002班 学号:0909103108 课程:操作系统安全课程设计 指导老师:张士庚 一、课程设计目的 1.了解Linux字符设备驱动程序的结构; 2.掌握Linux字符设备驱动程序常用结构体和操作函数的使用方法; 3.初步掌握Linux字符设备驱动程序的编写方法及过程; 4.掌握Linux字符设备驱动程序的加载方法及测试方法。 二、课程设计内容 5.设计Windows XP或者Linux操作系统下的设备驱动程序; 6.掌握虚拟字符设备的设计方法和测试方法;

7.编写测试应用程序,测试对该设备的读写等操作。 三、需求分析 3.1驱动程序介绍 驱动程序负责将应用程序如读、写等操作正确无误的传递给相关的硬件,并使硬件能够做出正确反应的代码。驱动程序像一个黑盒子,它隐藏了硬件的工作细节,应用程序只需要通过一组标准化的接口实现对硬件的操作。 3.2 Linux设备驱动程序分类 Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。虽然Linux内核的不断升级,但驱动程序的结构还是相对稳定。 Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network device)三种。字符设备是指在存取时没有缓存的设备,而块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access)。典型的字符设备包括鼠标,键盘,串行口等。块设备主要包括硬盘软盘设备,CD-ROM等。 网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket 机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据传递。系统有支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。 3.3驱动程序的结构 驱动程序的结构如图3.1所示,应用程序经过系统调用,进入核心层,内核要控制硬件需要通过驱动程序实现,驱动程序相当于内核与硬件之间的“系统调用”。

字符设备基础

Linux 字符设备基础 字符设备驱动程序在系统中的位置 操作系统内核需要访问两类主要设备,简单的字符设备,如打印机,键盘等;块设备,如软盘、硬盘等。与此对应,有两类设备驱动程序。分别称为字符设备驱动程序和块设备驱动程序。两者的主要差异是:与字符设备有关的系统调用几乎直接和驱动程序的内部功能结合在一起。而读写块设备则主要和快速缓冲存储区打交道。只有需要完成实际的输入/输出时,才用到块设备驱动程序。见下图: Linux 设备驱动程序的主要功能有: ● 对设备进行初始化; ● 使设备投入运行和退出服务; ● 从设备接收数据并将它们送到内核; ● 将数据从内核送到设备; ● 检测和处理设备出现的错误。 当引导系统时,内核调用每一个驱动程序的初始化函数。它的任务之一是将这一设备驱动程序使用的主设备号通知内核。同时,初始化函数还将驱动程序中的函数地址结构的指针送给内核。 内核中有两X 表。一X 表用于字符设备驱动程序,另一X 用于块设备驱动程序。这两X 表用来保存指向file_operations 结构的指针, 设备驱动程序内部的函数地址就保

存在这一结构中。内核用主设备号作为索引访问file_operations结构,因而能访问驱动程序内的子程序。 从开机到驱动程序的载入 系统启动过程中可能出现几种不同的方式检测设备硬件。首先机器硬件启动时BIOS会检测一部分必要的设备,如内存、显示器、键盘和硬盘等等。机器会把检测到的信息存放在特定的位置,如CMOS数据区。而另外某些设备会由设备驱动程序进行检测。 1 开机 2 引导部分(linux/config.h,arch/i386/boot/bootsect.S) 3 实模式下的系统初始化(arch/i386/boot/setup.S) 4 保护模式下的核心初始化 5 启动核心(init/main.c) init函数中函数调用关系如下: main.c init() filesystems.c sys_setup() genhd.c device_setup() mem.c chr_dev_init() 至此,驱动程序驻入内存。 设备驱动程序基本数据结构: struct device_struct 系统启动过程中要登记的块设备和字符设备管理表的定义在文件fs/devices.c中:struct device_struct { const char * name; struct file_operations * fops; }; static struct device_struct chrdevs[MAX_CHRDEV]; static struct device_struct blkdevs[MAX_BLKDEV]; 其实块设备表和字符设备表使用了相同的数据结构。在某些系统中,这些设备表也称作设备开关表,不同的是它们直接定义了一组函数指针进行对设备的管理。而这里系统用文件操作(file_operations)代替了那组开关。文件操作是文件系统与设备驱动程序之间的接口,系统特殊文件在建立的时候并没有把两者对应起来,只是把设备的缺省文件结构和i节点结构赋给设备文件,而真正的对应定义在系统启动之后,当设备被打开时时才进行的。 操作blkdev_open和chrdev_open定义在文件devices.c中,它们的基本功能是当设备文件初次打开时,根据该文件的i节点信息找到设备真正的文件操作接口,然后更新原来的设

字符设备驱动程序

Linux字符设备驱动(转载) 来源: ChinaUnix博客日期:2008.01.01 18:52(共有0条评论) 我要评论 Linux字符设备驱动(转载) 这篇文章描述了在Linux 2.4下,如何建立一个虚拟的设备,对初学者来说很有帮助。原文地址:https://www.360docs.net/doc/0018783636.html,/186/2623186.shtml Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open ()、close ()、read ()、write () 等。 Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。 下面我们来假设一个非常简单的虚拟字符设备:这个设备中只有一个4个字节的全局变量int global_var,而这个设备的名字叫做"gobalvar"。对"gobalvar"设备的读写等操作即是对其中全局变量global_var的操作。 驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备: static int __init gobalvar_init(void) { if (register_chrdev(MAJOR_NUM, " gobalvar ", &gobalvar_fops)) { //…注册失败 } else

一个简单的演示用的Linux字符设备驱动程序.

实现如下的功能: --字符设备驱动程序的结构及驱动程序需要实现的系统调用 --可以使用cat命令或者自编的readtest命令读出"设备"里的内容 --以8139网卡为例,演示了I/O端口和I/O内存的使用 本文中的大部分内容在Linux Device Driver这本书中都可以找到, 这本书是Linux驱动开发者的唯一圣经。 ================================================== ===== 先来看看整个驱动程序的入口,是char8139_init(这个函数 如果不指定MODULE_LICENSE("GPL", 在模块插入内核的 时候会出错,因为将非"GPL"的模块插入内核就沾污了内核的 "GPL"属性。 module_init(char8139_init; module_exit(char8139_exit; MODULE_LICENSE("GPL"; MODULE_AUTHOR("ypixunil"; MODULE_DESCRIPTION("Wierd char device driver for Realtek 8139 NIC"; 接着往下看char8139_init( static int __init char8139_init(void {

int result; PDBG("hello. init.\n"; /* register our char device */ result=register_chrdev(char8139_major, "char8139", &char8139_fops; if(result<0 { PDBG("Cannot allocate major device number!\n"; return result; } /* register_chrdev( will assign a major device number and return if it called * with "major" parameter set to 0 */ if(char8139_major == 0 char8139_major=result; /* allocate some kernel memory we need */ buffer=(unsigned char*(kmalloc(CHAR8139_BUFFER_SIZE, GFP_KERNEL; if(!buffer { PDBG("Cannot allocate memory!\n"; result= -ENOMEM;

字符设备驱动开发实验

字符设备驱动实验 实验步骤: 1、将设备驱动程序使用马克file文件编译 生成模块firstdev.ko 2、将模块加载到系统中insmod firstdev.ko 3、手动创建设备节点 mknod /dev/first c 122 0 4、使用gcc语句编译firsttest.c生成可执行 文件 5、运行可执行文件firsttest,返回驱动程序 中的打印输出语句。 查看设备号:cat /proc/devices 卸载驱动:rmmod firstdev 删除设备节点:rm /dev/first 显示printk语句,(打开一个新的终端)while true do sudo dmesg -c sleep 1 done

源码分析 设备驱动程序firstdev.c #include #include #include #include #include #include //#include static int first_dev_open(struct inode *inode, struct file *file) { //int i; printk("this is a test!\n"); return 0; }

static struct file_operations first_dev_fops ={ .owner = THIS_MODULE, .open = first_dev_open, }; static int __init first_dev_init(void) { int ret; ret = register_chrdev(122,"/dev/first",&first_dev_fo ps); printk("Hello Modules\n"); if(ret<0) { printk("can't register major number\n"); return ret; }

一个简单字符设备驱动实例

如何编写Linux设备驱动程序 Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便。本文是在编写一块多媒体卡编制的驱动程序后的总结,获得了一些经验,愿与Linux fans共享,有不当之处,请予指正。 以下的一些文字主要来源于khg,johnsonm的Write linux device driver,Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依据自己的试验结果进行了修正. 一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能: 1)对设备初始化和释放; 2)把数据从内核传送到硬件和从硬件读取数据; 3)读取应用程序传送给设备文件的数据和回送应用程序请求的数据; 4)检测和处理设备出现的错误。 在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是块设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待. 已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备。另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序. 最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。 二、实例剖析 我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会获得一个真正的设备

字符设备驱动程序

字符设备驱动程序 字符设备驱动程序与块设备不同。所涉及的键盘驱动、控制台显示驱动和串口驱动以及与这些驱动有关的接口、算法程序都紧密相关。他们共同协作实现控制台终端和串口终端功能。 下图反映了控制台键盘中断处理过程。 以上为总的处理流程,下面对每一个驱动分开分析。首先是键盘驱动。键盘驱动用汇编写的,比较难理解,牵涉内容较多,有键盘控制器804X的编程,还有扫描码(共3套,这里用第二套)和控制命令及控制序列(p209~210有讲解)。由于键盘从XT发展到AT到现在PS/2,USB,无线键盘,发展较快,驱动各有不同,此版本驱动为兼容XT,将扫描码映射为XT再处理,因此仅供参考。CNIX操作系统的键盘驱动实现为C语言,可读性更好。 键盘驱动 键盘驱动就是上图键盘硬件中断的过程。keyboard.S中的_keyboard_interrupt 函数为中断主流程,文件中其他函数均被其调用。

以上打星处为键盘驱动的核心,即主要处理过程,针对不同扫描码分别处理,并最终将转换后所得ASCII 码或控制序列放入控制台tty 结构的读缓冲队列read_q 中。 键处理程序跳转表为key_table ,根据扫描码调用不同处理程序,对于“普通键”,即只有一个字符返回且没有含义变化的键,调用do_self 函数。其他均为“特殊键”:1. crtrl 键的按下和释放 2. alt 键的按下和释放 3. shift 键的按下和释放 4. caps lock 键的按下和释放(释放直接返回,不作任何处理) 5. scroll lock 键的按下 6. num lock 的按下 7. 数字键盘的处理(包括alt-ctrl+delete 的处理,因为老式键盘delete 键在数字小键盘上。还包括对光标移动键的分别处理) 8. 功能键 (F1~F12)的处理 9. 减号的处理(老键盘’/’与’-’以0xe0加以区分,可能其中一键要按shift ) do_self 是最常用的流程,即跳转表中使用频率最高的流程:

linux字符设备驱动课程设计报告

一、课程设计目的 Linux 系统的开源性使其在嵌入式系统的开发中得到了越来越广泛的应用,但其本身并没有对种类繁多的硬件设备都提供现成的驱动程序,特别是由于工程应用中的灵活性,其驱动程序更是难以统一,这时就需开发一套适合于自己产品的设备驱动。对用户而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说是把设备映射为一个特殊的设备文件,用户程序可以像对其它文件一样对此设备文件进行操作。 通过这次课程设计可以了解linux的模块机制,懂得如何加载模块和卸载模块,进一步熟悉模块的相关操作。加深对驱动程序定义和设计的了解,了解linux驱动的编写过程,提高自己的动手能力。 二、课程设计内容与要求 字符设备驱动程序 1、设计目的:掌握设备驱动程序的编写、编译和装载、卸载方法,了解设备文件的创建,并知道如何编写测试程序测试自己的驱动程序是否能够正常工作 2、设计要求: 1) 编写一个简单的字符设备驱动程序,该字符设备包括打开、读、写、I\O控制与释放五个基本操作。 2) 编写一个测试程序,测试字符设备驱动程序的正确性。 3) 要求在实验报告中列出Linux内核的版本与内核模块加载过程。 三、系统分析与设计 1、系统分析 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能: 1、对设备初始化和释放; 2、把数据从内核传送到硬件和从硬件读取数据; 3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据; 4、检测和处理设备出现的错误。 字符设备提供给应用程序的是一个流控制接口,主要包括op e n、clo s e(或r ele as e)、r e ad、w r i t e、i o c t l、p o l l和m m a p等。在系统中添加一个字符设备驱动程序,实际上就是给上述操作添加对应的代码。对于字符设备和块设备,L i n u x内核对这些操作进行了统一的抽象,把它们定义在结构体fi le_operations中。 2、系统设计: 、模块设计:

字符设备驱动框架

Linux中设备分类: 按照对设备的访问方式可分为以下三类: 1.字符设备(char device) (1)例如:键盘、鼠标、串口、帧缓存等; (2)通过/dev/下的设备节点访问;以字节为单位访问; (3)一般只支持顺序访问;(特例:帧缓存framebuffer) (4)无缓冲。 2.块设备(block device) (1)例如:磁盘、光驱、flash等; (2)以固定大小为单位访问:磁盘以扇区(512B)为单位;flash以页为单位。 (3)支持随机访问; (4)有缓冲(减少磁盘IO,提高效率)。 3.网络设备(network device) (1)无设备文件(节点); (2)应用层通过socket接口访问网络设备(报文发送和接收的媒介)。 设备驱动在内核中的结构: 1.VFS虚拟文件系统作用:向应用层提供一致的文件访问接口,正是由于VFS 的存在,才可以将设备以文件的方式访问。 2.虚拟文件系统,存在于内存中,不在磁盘上,掉电丢失。例如:/proc、/sys、 /tmp。

设备号: 1.作用:唯一地标识一个设备; 2.类型:dev_t devno;即32位无符号整型; 3.组成: (1)主设备号:用于区分不同类型(按功能划分)的设备; (2)此设备号:用于区分相同类型的不同设备。 注意:相同类型的设备(主设备号相同)可以使用同一个驱动。 4.构建设备号: int major = 250; int minor = 0; (1)dev_t devno = (major << 20) | minor;不建议使用; (2)利用宏来构建:dev_t devno = MKDEV (major, minor); 注意:我们可以通过文件$(srctree)/documentation/device.txt来查看内核对设备号的分配情况。 (1)该文本中的有对应设备文件的设备号是已经被申请过的,我们不可以重 复使用(申请); (2)从中可以看出,我们在编写驱动程序时可以使用的主设备号范围为 240~254,为了方便记忆,通常使用250作为主设备号。 字符设备驱动框架: 驱动:作用,为应用层提供访问设备的接口(对设备发的各种操作)。 一、申请设备号 1.构建设备号:dev_t devno = MKDEV (major, minor); 2.申请设备号: (1)动态申请:alloc_chrdev_region; (2)静态申请: register_chrdev_region。

字符设备驱动步骤

编写字符设备驱动框架的步骤 Step 1: 申请设备号(主要是申请主设备号) 有两种方式: ⑴静态申请 通过下面这个函数实现: int register_chrdev_region(dev_t from, unsigned count, const char *name); /* register_chrdev_region() - register a range of device numbers * @from: the first in the desired range of device numbers; must include * the major number. * @count: the number of consecutive device numbers required * @name: the name of the device or driver. * * Return value is zero on success, a negative error code on failure.*/ 这种方式主要用于,驱动开发者事先知道该驱动主设备号的情况。 ⑵动态申请 通过下面这个函数实现: int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) /* alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code.*/ 这种方式由系统动态分配一个设备号,返回的设备号保存在参数dev中。 Step 2 :注册字符设备 在linux 内核中用struct cdev表示一个字符设备。 字符设备的注册与注销分别通过下面的两个函数来实现: int cdev_add(struct cdev *p, dev_t dev, unsigned count); /** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure.

LINUX字符设备驱动编写基本流程

---简介 Linux下的MISC简单字符设备驱动虽然使用简单,但却不灵活。 只能建立主设备号为10的设备文件。字符设备比较容易理解,同时也能够满足大多数简 单的硬件设备,字符设备通过文件系统中的名字来读取。这些名字就是文件系统中的特 殊文件或者称为设备文件、文件系统的简单结点,一般位于/dev/目录下使用ls进行查 看会显示以C开头证明这是字符设备文件crw--w---- 1 root tty 4, 0 4月 14 11:05 tty0。 第一个数字是主设备号,第二个数字是次设备号。 ---分配和释放设备编号 1)在建立字符设备驱动时首先要获取设备号,为此目的的必要的函数是 register_chrdev_region,在linux/fs.h中声明:int register_chrdev_region(dev_t first, unsigned int count, char *name);first是你想 要分配的起始设备编号,first的次编号通常是0,count是你请求的连续设备编号的 总数。count如果太大会溢出到下一个主设备号中。name是设备的名字,他会出现在 /proc/devices 和sysfs中。操作成功返回0,如果失败会返回一个负的错误码。 2)如果明确知道设备号可用那么上一个方法可行,否则我们可以使用内核动态分配的设 备号int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name);dev是个只输出的参数,firstminor请求的第一个要用的次编号, count和name的作用如上1)对于新驱动,最好的方法是进行动态分配 3)释放设备号,void unregister_chrdev_region(dev_t first unsigned int count); ---文件操作file_operations结构体,内部连接了多个设备具体操作函数。该变量内部 的函数指针指向驱动程序中的具体操作,没有对应动作的指针设置为NULL。 1)fops的第一个成员是struct module *owner 通常都是设置成THIS_MODULE。 linux/module.h中定义的宏。用来在他的操作还在被使用时阻止模块被卸载。 2)loff_t (*llseek) (struct file *, loff_t, int);该方法用以改变文件中的当前读/ 写位置 返回新位置。 3)ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);该函数用 以从设备文件 中读取数据,读取成功返回读取的字节数。

简单字符设备驱动程序的设计

实验五:简单字符设备驱动程序的设计 实验学时:4 实验类型:(设计) 一、实验目的 1. 理解设备驱动程序的处理过程; 2. 掌握Linux设备驱动程序开发的基本过程和设计方法; 3. 学会编写简单的字符设备驱动程序。 二、实验条件 Linux操作系统gcc 三、实验原理及相关知识 设备驱动程序是I/O进程与设备控制器之间的通信程序。 驱动程序的功能: ⑴接收由设备独立性软件发来的命令和参数,并将命令中的抽象要求转换为具体的要求。 ⑵检查用户I/O请求的合法性,了解I/O设备的状态,传递有关参数,设置设备的工作方式。 ⑶发出I/O命令。 ⑷及时响应由控制器或通道发来的中断请求,并根据其中断类型调用相应的中断处理程序进行处理。 ⑸对于设置有通道的计算机系统,驱动程序还应能够根据用户的I/O请求,自动地构建通道程序。 设备驱动程序的处理过程: ⑴将抽象要求转换为具体要求 ⑵检查I/O设备请求的合法性 ⑶读出和检查设备的状态 ⑷传送必要的参数 ⑸工作方式的设置 ⑹启动I/O设备 Linux系统中,设备驱动程序是操作系统内核的重要组成部分,它与硬件设备之间建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设备进行打开(open)、关闭(close)、读写(read/write)等操作。

通常设备驱动程序接口是由结构file_operations结构体向系统说明的,它定义在include/linux/fs.h中。file_operations的数据结构如下: 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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 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 (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); ... }; ⑴open 入口点: open函数负责打开设备、准备I/O。任何时候对设备文件进行打开操作,都会调用设备的open入口点。所以,open函数必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的。则open函数必须将设备标记成忙状态。 ⑵close入口点 close函数负责关闭设备的操作,当最后一次使用设备完成后,调用close函数,关闭设备文件。独占设备必须标记为可再次使用。 close()函数作用是关闭打开的文件。 ⑶read入口点 read函数负责从设备上读数据和命令,有缓冲区的I/O设备操作一般是从缓冲区里读数据。

实验二:字符设备驱动实验

实验二:字符设备驱动实验 一、实验目的 通过本实验的学习,了解Linux操作系统中的字符设备驱动程序结构,并能编写简单的字符设备的驱动程序以及对所编写的设备驱动程序进行测试,最终了解Linux操作系统如何管理字符设备。 二、准备知识 字符设备驱动程序主要包括初始化字符设备、字符设备的I/O调用和中 断服务程序。在字符设备驱动程序的file_operations结构中,需要定义字 符设备的基本入口点。 open()函数; release()函数 read()函数 write()函数 ioctl()函数 select()函数。 另外,注册字符设备驱动程序的函数为register_chrdev()。 register_chrdev() 原型如下: int register_chrdev(unsigned int major, //主设备号 const char *name, //设备名称 struct file_operations *ops); //指向设备操作函数指针 其中major是设备驱动程序向系统申请的主设备号。如果major为0, 则系统为该驱动程序动态分配一个空闲的主设备号。name是设备名称,ops 是指向设备操作函数的指针。 注销字符设备驱动程序的函数是unregister_chrdev(),原型如下:int unregister_chrdev(unsigned int major,const char *name); 字符设备注册后,必须在文件系统中为其创建一个设备文件。该设备文件可以在/dev目录中创建,每个设备文件代表一个具体的设备。 使用mknod命令来创建设备文件。创建设备文件时需要使用设备的主设备号和从设备号作为参数。 阅读教材相关章节知识,了解字符设备的驱动程序结构。

简单的虚拟字符设备驱动的实现

简单的虚拟字符设备驱动的实现 Linux業已成为嵌入式系统的主流,而各种Linux驱动程序的类型中,字符设备无疑是应用最广泛的。本文实现了一个简单的虚拟字符设备的驱动程序,用以演示Linux字符设备驱动的基本原理。在嵌入式Linux的教学中具有重要的作用。 标签:Linux 驱动程序字符设备虚拟嵌入式 Linux作为一种开放源代码的操作系统,在嵌入式系统领域业已成为主流,而为嵌入式Linux系统开发设备驱动程序,也成为一项重要的工作。Linux系统中的驱动程序主要分为三种:字符设备驱动程序、块设备驱动程序和网络驱动程序。其中字符设备是一类只能顺序读写,没有缓存的驱动程序,其实现方法相对简单,而应用则最为广泛。在嵌入式Linux的教学中,字符设备驱动程序也是一项重要内容。为了让学生能够理解字符设备驱动程序的原理,需要一个简单的字符设备驱动的例子,用以进行演示。 一、基本原理 把设备当作文件处理,是Linux系统的重要思想,即“一切皆文件”。在用户空间中,应用程序对字符设备的操作跟读写普通文件没有什么区别,也是通过open()、close()、read()、write()等函数实现的。操作系统将这些用户空间中的函数分别映射到内核空间中由驱动程序提供的对应接口。因此,内核空间中的驱动程序就需要通过对对应接口函数的实现来实现对用户空间中应用程序的支持。 file_opreations是字符设备驱动中最重要的结构,它包含了字符设备各种可能的接口函数。通常在嵌入式编程中,我们不需要全部实现,只需要实现我们实际用到的接口就可以了,这样可以有效降低程序的大小。该结构被定义在头文件“linux/fs.h”中,使用时只需声明该结构的一个变量并进行填充即可。 二、环境准备 为了进行嵌入式Linux的开发,必须首先安装Linux系统。这里采用最常用的Windows系统+VMWare虚拟机的形式,系统版本为RedHat Enterprise Linux 6.4,其自带的内核版本为2.6.32-358.el6.i686。该版本比之前沿用的RedHat9更新,同时也是一个被验证过的非常稳定的系统。 交叉编译器采用网上下载的Arm-Linux-gcc 4.5.1版本,同样兼顾到版本更新和稳定性之间的平衡关系。 各软件的安装过程本文不再赘述。

字符设备驱动程序的扩展操作

第5章字符设备驱动程序的扩展操作 在关于字符设备驱动程序的那一章中,我们构建了一个完整的设备驱动程序,从中用户可以读也可以写。但实际一个驱动程序通常会提供比同步read和write更多的功能。现在如果出了什么毛病,我已经配备了调试工具,我们可以大胆的实验并实现新操作。 通过补充设备读写操作的功能之一就是控制硬件,最常用的通过设备驱动程序完成控制动作的方法就是实现ioctl方法。另一种方法是检查写到设备中的数据流,使用特殊序列做为控制命令。尽管有时也使用后者,但应该尽量避免这样使用。不过稍后我们还是会在本章的“非ioctl设备控制”一节中介绍这项技术。 正如我在前一章中所猜想的,ioctl系统调用为驱动程序执行“命令”提供了一个设备相关的入口点。与read和其他方法不同,ioctl是设备相关的,它允许应用程序访问被驱动硬件的特殊功能――配置设备以及进入或退出操作模式。这些“控制操作”通常无法通过read/write文件操作完成。例如,你向串口写的所有数据都通过串口发送出去了,你无法通过写设备改变波特率。这就是ioctl所要做的:控制I/O通道。 实际设备(与scull不同)的另一个重要功能是,读或写的数据需要同其他硬件交互,需要某些同步机制。阻塞型I/O和异步触发的概念将满足这些需求,本章将通过一个改写的scull设备介绍这些内容。驱动程序利用不同进程间的交互产生异步事件。与最初的scull相同,你无需特殊硬件来测试驱动程序是否可以工作。直到第8章“硬件管理”我才会真正去与硬件打交道。 ioctl 在用户空间内调用ioctl函数的原型大致如下: int ioctl(int fd, int cmd, ...); 由于使用了一连串的“.”的缘故,该原型在Unix系统调用列表之中非常突出,这些点代表可变数目参数。但是在实际系统中,系统调用实际上不会有可变数目个参数。因为用户程序只能通过第2章“编写和运行模块”的“用户空间和内核空间”一节中介绍的硬件“门”才能访问内核,系统调用必须有精确定义的参数个数。因此,ioctl的第3个参数事实上只是一个可选参数,这里用点只是为了在编译时防止编译器进行类型检查。第3个参数的具体情况与要完成的控制命令(第2个参数)有关。某些命令不需要参数,某些需要一个整数做参数,而某些则需要一个指针做参数。使用指针通常是可以用来向ioctl传递任意数目数据;设备可以从用户空间接收任意大小的数据。 系统调用的参数根据方法的声明传递给驱动程序方法: int (*ioctl) (struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg) inode和filp指针是根据应用程序传递的文件描述符fd计算而得的,与read和write的用法一致。参数cmd不经修改地传递给驱动程序,可选的arg参数无论是指针还是整数值,它都以unsigned long的形式传递给驱动程序。如果调用程序没有传递第3个参数,驱动程序所接收的arg没有任何意义。

字符设备驱动程序设计

实验七字符设备驱动程序设计 实验目的 掌握字符设备驱动程序编写的框架; 掌握设备驱动相关的知识; 实验原理 参考本周大课课件《字符设备驱动程序.PPT》。 实验步骤 建立一个工作目录,我们的驱动程序模块程序保存在这个目录下; #mkdir first_drv #cd first_drv 1 自己编写一个字符设备驱动程序,程序格式参考下面; #include #include #include #include #include #include #include //(1)编写硬件底层操作函数实现open,release,write,read ...... //(2)创建一个file_operations结构 ...... static int __init xxx_init(void) { //(3)申请设备号 ........

//(4)初始化cdev结构 ........ //(5)注册cdev结构 ........ return 0; } static void __exit XXX_exit(void) { //注销cdev结构 ........ //注销设备号 } module_init(.......); module_exit(.......); MODULE_LICENSE(......); 注意:要求底层函数要实现open,release,write,read方法。 2 编写Makefile文件 内容格式,参考如下: obj-m +=模块程序文件名.o all: make -C 内核源码路径M=`pwd` modules #这一行要以TAB键开头 clean: make -C 内核源码路径M=`pwd` modules clean #这一行要以TAB键开头 3 编译模块,拷贝到根文件系统中 编译内核模块,直接使用make命令就可以了; #make 编译没有错误时,将模块拷贝到跟文件系统中; #cp xxx.ko /opt/rootfs/lib/modules/3.5.0-yyy/ 4 启动开发板,进入linux系统后,在开发板上加载和卸载模块 加载: # insmod /lib/modules/3.5.0-yyy/xxxx.ko 查看系统分配的设备号

相关文档
最新文档