编写Linux操作系统设备驱动程序概述

时间:2023年02月11日

/

来源:木及木另戈

/

编辑:本站小编

收藏本文

下载本文

下面是小编整理的编写Linux操作系统设备驱动程序概述,本文共8篇,欢迎您能喜欢,也请多多分享。本文原稿由网友“木及木另戈”提供。

篇1:编写Linux操作系统设备驱动程序概述

1.1 Linux设备驱动程序分类

Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加, 主要是驱动程序的增加,在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。在2.0.xx到2.2.xx的变动里,驱动程序的编写做了一些改变,但是从2.0.xx的驱动到 2.2.xx的移植只需做少量的工作。Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(net work device)三种。字符设备是指存取时没有缓存的设备。块设备的读写都有缓存来支 持,并且块设备必须能够随机存取(random access),字符设备则没有这个要求。典型的 字符设备包括鼠标,键盘,串行口等。块设备主要包括硬盘软盘设备,CD-ROM等。一个文件系统要安装进入操作系统必须在块设备上。 网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制 。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

1.2 编写驱动程序的一些基本概念

无论是什么操作系统的驱动程序,都有一些通用的概念。操作系统提供给驱动程序的支持也大致同。下面简单介绍一下网络设备驱动程序的一些基本要求。

1.2.1 发送和接收

这是一个网络设备最基本的功能。一块网卡所做的无非就是收发工作。所以驱动程序里要告诉系统发送函数在哪里,系统在有数据要发送时就会调用你的发 送程序。还有 驱动程序由于是直接操纵硬件的,所以网络硬件有数据收到最先能得到这个数据的也就是驱动程序,它负责把这些原始数据进行必要的处理然后送给系统。这里,操作系统必须要提供两个机制,一个是找到驱动程序的发送函数,一个是驱动程序把收到的数据送给系统。

1.2.2 中断

中断在现代计算机结构中有重要的地位。操作系统必须提供驱动程序响应中断的能力。 一般是把一个中断处理程序注册到系统中去。操作系统在硬件中断发生后 调用驱动程序 的处理程序。Linux支持中断的共享,即多个设备共享一个中断。

1.2.3 时钟

在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中断机制的 硬件的轮询等。操作系统应为驱动程序提供定时机制。一般是在预定的时 间过了以后回调注册的时钟函数。在网络驱动程序中,如果硬件没有中断功能,定时器可以提供轮询(poll)方式对硬件进行存取。或者是实现某些协议时需要的超时重传等。

二.Linux系统网络设备驱动程序

2.1 网络驱动程序的结构

所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。一个设备就是一个对象(device 结构),它内部有自己的数据和方法。每一个设备的方法被调用时的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身的数据(类似面向对象程序设计时的this引用)。

一个网络设备最基本的方法有初始化、发送和接收。

------------------- ---------------------

|deliver packets | |receive packets queue|

|(dev_queue_xmit) | |them(netif_rx()) |

------------------- ---------------------

| | /

/ | |

-------------------------------------------------------

| methods and variables(initialize,open,close,hard_xmit,|

| interrupt handler,config,resources,status...) |

-------------------------------------------------------

| | /

/ | |

----------------- ----------------------

|send to hardware | |receivce from hardware|

----------------- ----------------------

| | /

/ | |

-----------------------------------------------------

| hardware media |

-----------------------------------------------------

初始化程序完成硬件的初始化、device中变量的初始化和系统资源的申请。发送程序是在驱动程序的上层协议层有数据要发送时自动调用的。一般驱动程序中不对发送数据进行缓存,而是直接使用硬件的发送功能把数据发送出去。接收数据一般是通过硬件中断来通知的。在中断处理程序里,把硬件帧信息填入一个skbuff结构中,然后调用netif_ rx()传递给上层处理。

2.2 网络驱动程序的基本方法

网络设备做为一个对象,提供一些方法供系统访问。正是这些有统一接口的方法,掩蔽 了硬件的具体细节,让系统对各种网络设备的访问都采用统一的形式,做到硬件无关性 。

下面解释最基本的方法。

2.2.1 初始化(initialize)

驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初始化程序 。它做以下几方面的工作。检测设备。在初始化程序里你可以根据硬件的特征检查硬件是否存在,然后决定是否启动这个驱动程序。配置和初始化硬件。在初始化程序里你可以完成对硬件资源的配置,比如即插即用的硬件就可以在这个时候进行配置(Linux内核对PnP功能没有很好的支持,可以在驱动程序里完成这个功能)。配置或协商好硬件占用的资源以后,就可以向系统申请这些资源。有些资源是可以和别的设备共享的,如中断。有些是不能共享的,如IO、DMA。接下来你要初始化device结构中的变量。最后,你可以让硬件正式开始工作。

2.2.2 打开(open)

open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状态由down-->up),

所以实际上很多在initialize中的工作可以放到这里来做。比如资源的申请,硬件的激活。如果dev->open返回非0(error),则硬件的状态还是down。 open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处 于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。

2.2.3 关闭(stop)

close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里应该调用MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。另外close方法必须返回成功(0==success)。

2.2.4 发送(hard_start_xmit)

所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit时,发送 的数据放在一个sk_buff结构中。一般的驱动程序把数据传给硬件发出去。也有一些特殊 的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃数据。如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。如果设备暂时 无法处理,比如硬件忙,则返回1。这时如果dev->tbusy置为非0,则系统认为硬件忙,要等到dev->tbusy置0以后才会再次发送。tbusy的置0任务一般由中断完成。硬件在发送 结束后产生中断,这时可以把tbusy置0,然后用mark_bh()调用通知系统可以再次发送。 在发送不成功的情况下,也可以不置dev->tbusy为非0,这样系统会不断尝试重发。如果hard_start_xmit发送不成功,则不要释放sk_buff。传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需要再填充硬件帧头,数据可以直接提交给硬 件发送。sk_buff是被锁住的(locked),确保其他程序不会存取它。

2.2.5 接收(reception)

驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统的。一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb),从 硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中 的一些信息。skb->dev= dev,判断收到帧的协议类型,填入skb->protocol(多协 议的支持)。把指针skb->mac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要设置skb->pkt_type,标明第二 层(链路层)数据类型。可以是以下类型:

PACKET_BROADCAST : 链路层广播;

PACKET_MULTICAST : 链路层组播;

PACKET_SELF : 发给自己的帧;

PACKET_OTHERHOST : 发给别人的帧(监听模式时会有这种帧)。

最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返回, 真正的处理是在中断返回以后,这样可以减少中断时间。调用netif_rx()以后, 驱动程序就不能再存取数据缓冲区skb。

2.2.6 硬件帧头(hard_header)

硬件一般都会在上层数据发送之前加上自己的硬件帧头,比如以太网(Ethernet)就有14字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序提供一个hard_ header方法,协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。 硬件帧头的长度必须填在dev->hard_header_len,这样协议层回在数据之前保留好硬件 帧头的空间。这样hard_header程序只要调用skb_push然后正确填入硬件帧头就可以了。

在协议层调用hard_header时,传送的参数包括(2.0.xx):数据的sk_buff,device指针 ,protocol,目的地址(daddr),源地址(saddr),数据长度(len)。数据长度不要使用s k_buff中的参数,因为调用hard_header时数据可能还没完全组织好。saddr是NULL的话是使用缺省地址(default)。daddr是NULL表明协议层不知道硬件目的地址。如果hard_header完全填好了硬件帧头,则返回添加的字节数。如果硬件帧头中的信息还不完全(比如daddr为NULL,但是帧头中需要目的硬件地址。典型的情况是以太网需要地址解析(arp)),则返回负字节数。hard_header返回负数的情况下,协议层会做进一步的build header的工作。目前Linux系统里就是做arp (如果hard_header返回正,dev->arp=1,表明不需要做arp,返回负,dev->arp=0,做arp)。 对hard_header的调用在每个协议层的处理程序里。如ip_output。

2.2.7 地址解析(xarp)

有些网络有硬件地址(比如Ethernet),并且在发送硬件帧时需要知道目的硬件地址。这样就需要上层协议地址(ip、ipx)和硬件地址的对应。这个对应是通过地址解析完成的。需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方法。调用的主要参数包括指向硬件帧头的指针,协议层地址。如果驱动程序能够解析硬件地址,就返回1,如果不能,返回0。对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。

2.2.8 参数设置和统计数据

在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级用户(root)权限才能对设备参数进行设置。设置方法有: dev->set_mac_address()。

当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址的设置没有太大意义的。 dev->set_config() 。当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法。用户会传递一个ifmap结构包含需要的I/O、中断等参数。dev->do_ioctl()

如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统会调用驱动程序的这个方法。一般是设置设备的专用数据。读取信息也是通过ioctl调用进行。除次之外驱动程序还可以提供一个dev->get_stats方法,返回一个enet_statistics结构,包含发送接收的统计信息。ioc

tl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。

篇2:linux设备驱动程序的编写

先介绍下我的环境,用的ubuntu发行版,kernel版本是3.8.0,《Linux设备驱动程序》一书是针对2.6.0的kernel,在这个例子中会看到一些差异,这个例子要实现的目标是,编译装载该module后,通过udev动态的生成设备节点star0,读取设备节点cat /dev/star0可以打印出星号,星号的数量可以由模块参数动态的指定。以下为源代码:

star.h

#ifndef STAR_H_H_H

#define STAR_H_H_H

#define AUTHOR “Tao Yang”

#define DESCRIPTION “A CHAR DEVICE DIRVERS SAMPLE USING UDEV”

#define VERSION “0.1”

#endif

star.c

//module_init module_exit

#include

#include

//module_param

#include

//printk container_of

#include

//dev_t MAJOR MINOR MKDEV

#include

//file_operations file register/unregister_chrdev_region alloc_chrdev_region register/unregister_chrdev(old)

#include

//cdev cdev_init/add/del

#include

//copy_from_user copy_to_user

#include

#include

#include “star.h”

//define device number

#define STAR_MAJOR 0

#define STAR_MINOR 0

#define STAR_DEVS 1

#define DEVICE_NAME “star0”

#define CLASS_NAME “star”

static int howmany=5;

module_param(howmany,int,S_IRUGO);

//module info

MODULE_AUTHOR(AUTHOR);

MODULE_DESCRIPTION(DESCRIPTION);

MODULE_VERSION(VERSION);

MODULE_LICENSE(“GPL”);

//device variables

static struct class* star_class=NULL;

static struct device* star_sysdevice=NULL;

int star_major=STAR_MAJOR;

int star_minor=STAR_MINOR;

int star_nr_devs=STAR_DEVS;

//device struct

static struct star_dev {

char *data;

struct cdev cdev;

};

static struct star_dev star_device;

static ssize_t star_read(struct file * filp,char * buf,size_t count,loff_t *ppos)

{

int i;

char star_str[10000];

struct star_dev *dev=filp->private_data;

for (i=0;i< p=“”>

star_str[i]='*';

}

star_str[howmany]='\\n';

int len=strlen(star_str);

if(count< p=“”>

return -EINVAL;

if(*ppos!=0)

return 0;

if(copy_to_user(buf,star_str,len))

return -EINVAL;

*ppos=len;

return len;

}

int star_open(struct inode *inode,struct file *filp)

{

struct star_dev *dev;

dev=container_of(inode->i_cdev,struct star_dev,cdev);

filp->private_data=dev;

//......

return 0;

}

//file operations

static const struct file_operations star_fops = {

.owner = THIS_MODULE,

.read = star_read,

.open = star_open,

};

static void star_setup_cdev(struct star_dev *dev,int index)

{

int err,devno=MKDEV(star_major,star_minor+index);

printk(KERN_ALERT “setup cdev...\\n”);

cdev_init(&dev->cdev,&star_fops);

dev->cdev.owner=THIS_MODULE;

dev->cdev.ops=&star_fops;

err=cdev_add(&dev->cdev,devno,1);

if(err)

printk(KERN_ALERT “Error %d adding star%d”,err,index);

}

static int __init star_init(void)

{

int ret;

dev_t dev;

printk(KERN_ALERT “hello tom!\\n”);

if(star_major){

dev=MKDEV(star_major,star_minor);

ret=register_chrdev_region(dev,star_nr_devs,DEVICE_NAME);

printk(KERN_ALERT “static!\\n”);

}else{

ret=alloc_chrdev_region(&dev,star_minor,star_nr_devs,DEVICE_NAME);

star_major=MAJOR(dev);

printk(KERN_ALERT “dynamic!\\n”);

printk(KERN_ALERT “Device Major is %d!\\n”,star_major);

}

if(ret<0){

printk(KERN_ALERT “star:can't get major %d\\n”,star_major);

return ret;

}

printk(KERN_ALERT “set up cdev!”);

star_setup_cdev(&star_device,0);

star_class=class_create(THIS_MODULE,CLASS_NAME);

if(IS_ERR(star_class)){

printk(KERN_ALERT “failed to register device class '%s'\\n”,CLASS_NAME);

}

//with a class ,the easiest way to instantiate a device is to call device_create()

star_sysdevice=device_create(star_class,NULL,MKDEV(star_major,0),NULL,DEVICE_NAME);

return 0;

}

static void __exit star_exit(void)

{

device_destroy(star_class,MKDEV(star_major,star_minor));

class_unregister(star_class);

class_destroy(star_class);

cdev_del(&star_device.cdev);

printk(KERN_ALERT “goodbye...\\n”);

}

module_init(star_init);

module_exit(star_exit);

Makefile

ifneq ($(KERNELRELEASE),)

obj-m:=star.o

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

PWD :=$(shell pwd)

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

编译源代码:

root@ubuntu:~/embedded_linux/ldd3/practice# make

make -C /lib/modules/3.8.0-29-generic/build M=/home/tom/embedded_linux/ldd3/practice modules

make[1]: Entering directory `/usr/src/linux-headers-3.8.0-29-generic'

CC [M] /home/tom/embedded_linux/ldd3/practice/star.o

/home/tom/embedded_linux/ldd3/practice/star.c:58:1: warning: useless storage class specifier in empty declaration [enabled by default]

/home/tom/embedded_linux/ldd3/practice/star.c: In function ‘star_read’:

/home/tom/embedded_linux/ldd3/practice/star.c:72:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]

/home/tom/embedded_linux/ldd3/practice/star.c:66:22: warning: unused variable ‘dev’ [-Wunused-variable]

/home/tom/embedded_linux/ldd3/practice/star.c:81:1: warning: the frame. size of 10032 bytes is larger than 1024 bytes [-Wframe-larger-than=]

Building modules, stage 2.

MODPOST 1 modules

CC /home/tom/embedded_linux/ldd3/practice/star.mod.o

LD [M] /home/tom/embedded_linux/ldd3/practice/star.ko

make[1]: Leaving directory `/usr/src/linux-headers-3.8.0-29-generic'

root@ubuntu:~/embedded_linux/ldd3/practice#

安装驱动模块:

root@ubuntu:~/embedded_linux/ldd3/practice# insmod star.ko

root@ubuntu:~/embedded_linux/ldd3/practice# dmesg -c

[ 3946.696548] hello tom!

[ 3946.699892] dynamic!

[ 3946.699894] Device Major is 249!

[ 3946.699895] set up cdev!

篇3:QNX 4.25设备驱动程序的编写

QNX 4.25设备驱动程序的编写

摘要:介绍实时操作系统QNX4.25下编写设备驱动程序的大体框架、底层细节以及诸多注意点。针对使用较为普遍的PCI设备作为较为详细的描述。关键词:驱动程序 QNX 实时操作系统 PCI引言QNX是一个多任务、多用户、分布式、可嵌入式符合POSIX标准的微内核的主流实时操作系统,广泛用于实时性能、开发灵活性、网络灵活性要求较高的场合,如电信系统、医疗仪器、航空航天、工业自动化、交通运输、POS机、信息家电等。QNX是一个适合软件/硬件定制的实时操作系统。如果你曾经试图在传统的UNIX或Windows平台下开发设备驱动程序,那么,QNX下开发驱动程序一定会让你受宠若惊。由于QNX的微内核结构,QNX下的系统进程和用户所写的进程没有什么不同,甚至没有私有的隐藏起来的以至用户不能使用的界面。正是这种结构给QNX带来了无与伦比的可扩展性,使得在QNX下写驱动程序如同写其它程序一般方便。设备驱动程序能够获取普通程序所能获得的任务服务。在QNX中增加一个新的驱动程序不会影响操作系统其它程序的任何部分,QNX环境所需的唯一改变是实现地启动新的驱动程序。当然,我们会遇到形形色色的硬件设备,某些驱动程序可能将以特殊方式控制设备的存在和配置。本文只想集中讨论QNX下如何进入、控制设备级的通用硬件,对所有驱动程序来讲这是一个共性问题。其中,将对使用较多的PCI设备作较为详细的叙述。以下是硬件驱动程序的编写。1 探测硬件首先,需要判断设备是否存在,然后查询该设备的配置(例如,设备基地址、中断号等)。对于某类设备,一般会有一大相应的标准机制来判断其配置。每块设备的基地址、中断号等是编程必须的资源,例如,常用的ISA及PCI硬件设备。对于ISA设备,一般由板上手工跳线设定,不言自明;对于常用的PCI设备,这些资源会由系统自动分配,特别是添减设备,可能会发生变化。因此,在驱动程序中能够动态查找这些资源显得比较重要。对于诸如A/D、D/A、定时卡、I/O板卡这类设备,对照硬件手册编写一些简单的驱动程序并不困难。如果有DOS下驱动程序的C源码,移值应该更容易一些。为了实现对PCI总线设备的控制和管理,必须访问PCI设备的配置空间。配置空间是一容量为256字节并具特定纪录结构的地址空间。该地址空间的结构如图1所示。NQX4.25pp sys/pci.h中对应的结构体定义。

每个PCI设备具有唯一的厂商标识(vendor id)和设备标识(device id),这些信息由硬件手册提供或系统启动时可以看到。下面一段代码展示了于一个给定的PCI设备如何调用QNX相关的函数、侦测设备的存在以及系统分配的资源。其中,标识(index)用来支持和区分具有同样厂商标识和设备标识的几块同样的设备。Index从0开始,如果指定为1,将标识第二块同型号的设备。本例中,YOUR_PCI_DEVICE_ID、YOUR_PCI)CENDOR)OD值是研华的PCL-1713采集卡,可以根据所使用的硬件填以合适的值。以根据所使用的硬件填以合适的值。#include#include#include#include#include#include#include#include#define YOUR_PCI_DEVICE_ID0x1713 //根据具体设备提供对应的厂商标识及设备标识#define YOUR_PCI_VENDOR_ID 0x13feint main(void){unsigned busnum,devfuncnum; //总线号(PC仅有一条)及设备功能号long address;long io_base; //I/O基地址unsigned char irq; //中断号int pci_index=0 //标识为零标识第一块此种型号设备if(_CA_PCI_Find_Device(YOUR_PCI_DEVICE_ID,YOUR_PCI_VENDOR_ID,pci_index,&busnum,&devfuncnum)!=PCI_SUCCESS){printf(“Can not find device”);exit(EXIT_FAILURE);}//侦测设备中断if(_CA_PCI_Read_Config_Byte(busnum,devfuncnum,offsetof(struct_pci_config_regs,Interrupt_Line),1,&irq)!=PCI_SUCCESS){printf(“Error reading interrupt”);exit(EXIT_FAILURE);}//侦测设备I/O基地址if_CA_PCI_Read_Config_DWord(busnum,devfuncnum,offsetof(struct_pci_config_r

egs,Base_[2]),1,(char *)&address)!=PCI_SUCCESS){printf(“Error reading address”);exit(EXIT_FAILURE);}io_base=PCI_IO_ADDR(adress);printf(“IO address:%x”,io_base);printf(“IRQ:”%x“,irq);exit(EXIT_SUCCESS);}注意:各种设备的Base_Address_Regs[x],x可能不尽相同,需要查看具体的硬件手册决定。2 进入硬件一旦获得了系统分配给某个硬件设备的资源信息,就可以同这个设备进行通信了。至于如何做取决于需要访问的硬件资源。2.1 I/O资源一个进程试图进行I/O操作,必须具有正确的权限等级。你必须是超及用户(root),在编译的时候加上适当参数T1,以确何该进程拥有访问I/O口的.权限。若忽视这一点,该运行进程将获得一个口的权限。若忽视这一点,该运行进行将获得一个SIGSEGV信号,表示一个非法的内存引用,并结束进程运行。现在就可以利用inp、inpd()、inpw(),outp(),inpd(),inpw(0等函数,对I/O基地址(I/O base address)加上寄存器偏移量(offset)处的I/O进行操作了。例如:outpw(baseaddress+offset_reg,0xdeadbeef);此外,对于一些设备,其I/O口是固定、众所皆知的,例如,一块VGA兼容的设备,并无上述所谓基地址。通过0x3c0、0x3d4、0x3d5,可以直接进入这些VGA的控制器。例如:outp(0x3d4,0x11);outp(0x3d5,inp(0x3d5)& ~0x80);2.2 存储映射资源某些设备,可以通过一般的内存操作进入寄存器,这就需要获得内存基地址(memory base address)。为了能够获进入此类设备的寄存器,需要将其映射到驱动程序虚拟地址空间。QNX下的技术资料/etc/readme/technotes/shmem.txt描述了如何创建一个共享内存对象,然后将这个内存对象的一段内存映射到PCI卡中,以便能够进入这个PCI设备。(接着上面的代码)可以利用mmap:char *mem_base;if(PCI_IS_MEM(address)){ //判断内存基地址int fd;char *page_ptr;fd=shm_open(”Physical“,O_RDWR,0777);//创建一个共享内存对象if(fd= =-1){perror(”Error shm_open:“);exit(EXIT_FAILURE);}page_ptr=mmap(0,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,PCI_MEM_ADDR(address)&~0xfff);//将内存基地址映射if(page_ptr= =(char *)perror(”Error mmap:“);exit(EXIT_FAILURE);}mem_base=page_ptr+(PCI_MEM_ADDR(address)&0xfff);close(fd);}printf(”MEM“ address:%lx”,PCI_MEM_ADDR(address));if(PCI_IS_MEM(address))printf(“mapped at : %lx”,mem_base);现在可以使用指针mem_base来进入设备寄存器了。例如:mem_base[SHUTDOWN_REGISTER]=0x0xdeadbeef;2.3 中断资源超级用户(root)可以调用qnx_hint_attach()将一个中断处理程序绑定到一个设备上。中断处理程序作为一个远程调用(far),在进程空间(Localdescriptor Table set)运行。该函数最后一个参数设置数据段。寄存器SS为一个特别的内核栈,这不同于数据段(DS)。因此,需要在中断处理程序及其调用的函数中关断栈检查。大部分系统库中的函数在编译的时候都关断了栈检查,然而,对于需要使用大量内存的函数可能并非如此。后者即是那些在中断处理程序中不可调用的函数,如printf()、open()。通过QNX具体函数在线资源的Safety→Interrupt handler项进行判断该函数是否可以调用。如果函数中包括任何自动(auto)变量,强烈建议将中断函数放在自身文件中,然后利用参数-zu选项编译之。这样能够告知编译器,使得SS!=DS。任何被中断处理程序修改的变量需要指定为volatile关键字。中断处理程序的返回值必须为0;或某个有效的代码号(proxy pid),以此来触发一个代码从而发送一则消息。下面总结一个中断处理程序编写时的注意点:①只能和自己的硬件对话(如,清除设备的中断状态位),千万不要对8259中断控制器编程!②使中断处理程序尽可能的短小。如果有很多的工作需要做,必须触发一个代理,并且它唤醒一个进程完成这些工作,以保证其它进程及低优先级的中断正常运行,提高系统的实时响应能力。③中断处理程序不能调用含有内核调用的例程。④中断处理程序必须是一个远程(far)调用函

数。⑤中断处理程序必须在自己的模块中。⑥无论程序中其它模块是如何编译的,包含中断处理程序的模块必须是利用-zu和-s选项编译。(利用cc-zu-Wc-s)这些选项能够保证SS!=DS,并且关断栈检查。当然,也可使用:#pragma off(check_stack);pid_t far handler_xxx(){return(proxy_xxx);}#pragma on(check_stack);在试图编写执行一个中断处理程序前,务必仔细阅读在线文档。现在,可以参照硬件手册自由地对您的设备寄存器进行操作了。结语在HT-7U极向场电源控制系统中,我们在QNX4.25下开发了多种设备的驱动程序。这些程序工作稳定、性能优异、工作量小且易于控制。此外,QSSL公司的新版本QNX6.x下开发驱动更为方便,其原理同QNX4.25相似或者是对应的。

篇4:QNX 4.25设备驱动程序的编写

QNX 4.25设备驱动程序的编写

摘要:介绍实时操作系统QNX4.25下编写设备驱动程序的大体框架、底层细节以及诸多注意点。针对使用较为普遍的PCI设备作为较为详细的描述。关键词:驱动程序 QNX 实时操作系统 PCI引言QNX是一个多任务、多用户、分布式、可嵌入式符合POSIX标准的微内核的主流实时操作系统,广泛用于实时性能、开发灵活性、网络灵活性要求较高的场合,如电信系统、医疗仪器、航空航天、工业自动化、交通运输、POS机、信息家电等。QNX是一个适合软件/硬件定制的实时操作系统。如果你曾经试图在传统的UNIX或Windows平台下开发设备驱动程序,那么,QNX下开发驱动程序一定会让你受宠若惊。由于QNX的微内核结构,QNX下的系统进程和用户所写的进程没有什么不同,甚至没有私有的隐藏起来的以至用户不能使用的界面。正是这种结构给QNX带来了无与伦比的可扩展性,使得在QNX下写驱动程序如同写其它程序一般方便。设备驱动程序能够获取普通程序所能获得的任务服务。在QNX中增加一个新的驱动程序不会影响操作系统其它程序的任何部分,QNX环境所需的唯一改变是实现地启动新的驱动程序。当然,我们会遇到形形色色的硬件设备,某些驱动程序可能将以特殊方式控制设备的存在和配置。本文只想集中讨论QNX下如何进入、控制设备级的通用硬件,对所有驱动程序来讲这是一个共性问题。其中,将对使用较多的PCI设备作较为详细的叙述。以下是硬件驱动程序的编写。1 探测硬件首先,需要判断设备是否存在,然后查询该设备的配置(例如,设备基地址、中断号等)。对于某类设备,一般会有一大相应的标准机制来判断其配置。每块设备的基地址、中断号等是编程必须的`资源,例如,常用的ISA及PCI硬件设备。对于ISA设备,一般由板上手工跳线设定,不言自明;对于常用的PCI设备,这些资源会由系统自动分配,特别是添减设备,可能会发生变化。因此,在驱动程序中能够动态查找这些资源显得比较重要。对于诸如A/D、D/A、定时卡、I/O板卡这类设备,对照硬件手册编写一些简单的驱动程序并不困难。如果有DOS下驱动程序的C源码,移值应该更容易一些。为了实现对PCI总线设备的控制和管理,必须访问PCI设备的配置空间。配置空间是一容量为256字节并具特定纪录结构的地址空间。该地址空间的结构如图1所示。NQX4.25pp sys/pci.h中对应的结构体定义。

每个PCI设备具有唯一的厂商标识(vendor id)和设备标识(device id),这些信息由硬件手册提供或系统启动时可以看到。下面一段代码展示了于一个给定的PCI设备如何调用QNX相关的函数、侦测设备的存在以及系统分配的资源。其中,标识(index)用来支持和区分具有同样厂商标识和设备标识的几块同样的设备。Index从0开始,如果指定为1,将标识第二块同型号的设备。本例中,YOUR_PCI_DEVICE_ID、YOUR_PCI)CENDOR)OD值是研华的PCL-1713采集卡,可以根据所使用的硬件填以合适的值。以根据所使用的硬件填以合适的值。#include#include#include#include#include#include#include

[1] [2] [3] [4]

篇5:为系统处理器编写Linux设备驱动程序

引 言

编写 Linux 设备驱动程序无疑是一项复杂的工作,本文将集中介绍非标准硬件的设备驱动程序编写,探讨硬件应用编程接口,并借用 Cirrus Logic EP9312 片上系统嵌入式平台添加设备驱动程序这一案例来进行分析。

如果有些编程内容未能在本文中涉及,那么读者亦可以查阅相似的设备驱动程序编码,以做参考。还有一种方法,就是检索历史档案或者向 Linux 内核问讯中心去函问讯。

Linux 概述

Linux 是 UNIX 操作系统的翻版,1991 年由 Linus Torvalds 最先开发出来,并通过开放源代码开发模式不断得到开放源代码组织的改进。任何使用 Linux 的个人和团体都无需支付任何版权费用。

只有内核还不够,通常Linux 与一些在内核上运行的视窗环境、视窗管理器和应用捆绑在一起。然而,由于具备了嵌入式平台,视窗环境并非必不可少。与微软的视窗操作系统不同的是,Linux 并不需要一套固定的、必须采用的应用软件或实用程序,因此能够十分符合嵌入式市场终端解决方案的客制化要求。

操作系统最基本的组成部分包括 1个资源管理器、1个调度程序、1个介于硬件和应用软件之间的接口、1个网络管理器和 1 个文档系统管理器。Linux操作系统也包括这些组成部分,当然还有其他部分。本文主要阐述介于硬件和应用软件之间的接口--设备驱动程序。

设备驱动程序类型

设备驱动程序可分为2大类:硬件设备驱动程序和软件设备驱动程序。硬件设备驱动程序和物理硬件设备相连接,如UART设备或IDE设备,而软件设备驱动程序则作为低级数据结构间的接口,或硬件设备驱动程序和高级数据结构间的接口。图形控制台驱动程序就是一个软件设备驱动程序。其中,1个LCD控制器驱动程序装载并管理该显示器,同时图形控制台对即将显示的字符进行着色,并获取从键盘输入的信息。软件设备驱动程序的另一个例子是文档系统执行--文档系统驱动程序采用1个硬盘驱动程序存储数据,而该硬盘驱动程序直接与物理硬盘相连接。

设备驱动程序的分类

Linux 设备驱动程序有几类:字符、区块、网络和其他。通常,驱动程序根据设备的访问方式分类。然而,也有些设备无法按照此类方式得到区分,因此被归到“其他类型”。字符设备包括那些使数据成为数据流的设备,可通过1个文档系统的特殊文件获得(文档系统的特殊文件将在后文中加以讨论)。鉴于字符设备的特性,该设备只能根据顺序访问数据,即无法往前或往后搜索数据。串行端口和音频设备都是这种类型。图2是Cirrus Logic的EP9312 片上系统结构图,其中Linux字符设备以绿色标出。

区块设备能够照管1个文档系统。该类设备和字符设备一样,也是通过文档系统特殊文件访问。但是,区块设备与文档设备的差异在于其可被随机访问。这意味着,应用软件可查找在该设备中的随机位置。硬盘驱动器和CD驱动器都是区块设备,它们内部的文件指针可以指向设备内部的任何位置,惟一的限制来自设备本身。当区块设备通过文档系统特殊文件访问时,该应用接口即同字符设备一样,只是与内核的接口有所差别而已。图2中的红色部分即为Cirrus Logic EP9312 片上系统结构中Linux区块设备。

网络接口设备既可以是硬件设备,也可以是软件设备。硬件设备如以太网卡,软件设备如低端网络协议堆栈(本文将此类接口视为软件设备)。中间件和协议堆栈有时会被看作是软件设备。网络接口设备是信息包数据的通信设备,一般拥有惟一名称,并且无法通过文档系统特殊文件访问。相反,它们只对内核网络堆栈开放。通常,用户级应用软件可访问内核网络堆栈,而不能访问网络接口设备。图2中的蓝色部分即为Cirrus Logic EP9312 片上系统结构中的Linux网络接口设备。

其他的设备驱动程序还包括数据总线驱动程序(USB, I2C, AMBA等)、/proc 接口和视频驱动程序。这些类型的设备无法被归入以上的3个类型中,但仍然是与Linux内核接口的设备驱动程序。

文档系统特殊文件

文档系统特殊文件提供了从文档系统访问硬件设备的可行性。这些访问点使用mknod 命令在文档系统/dev 目录中生成。命令如下:mknod 。

其中, 是给予硬件设备的名称,如 /dev/hda1 是给予硬盘驱动器的通用名称。 是设备驱动程序的类型--字符(char)、区块等。 代表设备类别和与之相配的驱动程序。 表示设备类别中的一个实例,并仅对设备驱动程序适用。例如,某个系统中同时采用2个硬盘驱动器,它们都具有同样的主要编号,使用同样的设备驱动软件,但是该设备驱动程序软件却会在内部根据次要编号区分这2个硬盘驱动器。

值得注意的是,并非所有的设备都执行特殊文件接口。如同本文前面已经提及的,网络设备驱动程序就不采用这种接口访问设备。

这种情况下,在设备文档系统里,就会使用 devfs来获得文档设备特殊文件。devfs 目前广受欢迎,但仍然还不是内核的默认功能。如果采用devfs 文档系统,那么就无需mknod 来生成特殊文件了。相反,设备驱动程序软件会使用直接的devfs 文档系统接口在空闲时刻或者设备刚被初始化时生成特殊文件。

编程实例概述

为便于示范非标准嵌入式平台的Linux设备驱动程序,本文将说明EP9312的设备驱动程序实现情况。其中,EP9312 IDE设备驱动程序是区块设备, EP9312触摸屏为字符设备,代码中的高级API/硬件接口、初始化序列和应用软件编码均将予以说明。

字符设备驱动程序实例:触摸屏设备驱动程序

EP9312触摸屏控制器因其数据只能按顺序获取而被列为Linux字符设备。触摸屏字符驱动程序的执行是相当简单的--设备向操作系统注册,并通过文档系统特殊文件进行访问。有关硬件代码包含在文档操作表的一套函数中。我们将从内核初始化开始,解释该驱动程序的执行情况。

初始化EP9312触摸屏的函数是:

int __init ep93xx_ts_init(void)

该函数处理2项工作:当设备被中断驱动时获取设备IRQ和在操作系统内注册触摸屏设备。

函数request_irq 在请求IRQ时被调用,并注册中断处理器函数以在设备发生系统中断时处理所需的任务。

而函数 register_chrdev() 则是用来注册字符设备的。该函数表现形式如下:

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

该函数安装了字符设备硬件的内核接口。主要编号用于把驱动程序映射到 /dev 目录中的文档系统特殊文件。设备被赋予一个名称,以便内核识辨。此外,file_operations 结构具有对函数指针表的一个指针,该表指向硬件的相应函数。

然而,仍然有一些字符设备不符合预先确定的字符设备范畴。这些设备就用主编号10一起归于“其他类型”,注册设备用以下函数:

int misc_register(struct miscdevice * misc)

misc_register()用主编号10调用 register_chrdev(),设备名称和函数表指针通过miscdevice数据结构获得。同样,miscdevice 数据结构还保存设备驱动程序所使用的次要号码。

以下是在设备驱动程序代码内注册 EP9312 触摸屏采用的函数调用:

misc_register(&ep93xx_ts_miscdev)

数据结构 ep93xx_ts_miscdev 是对触摸屏硬件的内核访问,定义如下:

static struct miscdevice ep93xx_ts_miscdev = {EP93XX_TS_MINOR, /* device minor number */“ep93xx_ts”, /* name of the device */&ep93xx_ts_fops /* device file operations *//* table pointer */ }

其他类型设备驱动程序采用次要号码区分设备。

硬件接口函数在设备驱动器内即被静态定义,当设备注册时,由内核通过传递给操作系统的文档操作函数指针获得。指针列表定义如下:

static struct file_operations ep93xx_ts_fops = {owner: THIS_MODULE,read: ep93xx_ts_read,write: ep93xx_ts_write,poll: ep93xx_ts_poll,open: ep93xx_ts_open,release: ep93xx_ts_release,fasync: ep93xx_ts_fasync, }

初始化触摸屏设备后,即需创建文档系统特殊文件,以便协助应用程序代码访问设备。创建 EP9312 触摸屏特殊文件的 mknod 命令如下:

mknod /dev/misc/ep93xx_ts c 10 240

该步骤即可在根目录系统下的初始化文档初始化 Linux 时得到执行,也可在命令提示里实现手动操作。

以下是用户级应用代码的一个实例,通过文档系统特殊文件访问触摸屏设备:

#define TS_DEV “/dev/misc/ep93xx_ts” int read_ts() {int fd, nbytes;short data[3]; fd = open(“/dev/misc/ep93xx_ts”, O_NONBLOCK); if ( fd < 0 ) {printf(“Unable to open touch screen device %s!\\n”, TS_DEV);exit(1);}nbytes = read(pd_fd, data, sizeof(data)); close(fd);if (nbytes != sizeof(data)) return 0;return 1; }

区块设备驱动器实例:IDE 设备驱动

与 EP9312 IDE 控制器接口的 IDE 设备被划分为 Linux 区块设备,其中包括硬盘驱动器和 CDROM 驱动器。这些设备上的数据可以随机读取是将其划分为区块设备的主要原因。

与简单的触摸屏接口执行相比,IDE 区块设备驱动器是相当复杂的。该设备驱动器被分成几部分,包括 IDE 区块设备内核接口、为 IDE 控制器设置的内部驱动器硬件接口(附加的独立 IDE 设备多达 4 个)、针对硬盘、软盘等 IDE 设备类型的模块,以及结构特别接口,

通过允许硬件或结构特殊函数的调用,IDE 设备类型数据结构内的函数指针可以实现非标准结构的灵活性和可延展性。图3为IDE区块设备驱动程序结构示意图。下面从设备驱动程序初始化开始说明该驱动程序。

高级IDE驱动程序在Linux内核初始化或模块安装(如果驱动程序被设置为模块)时得到初始化。本文不详述高级IDE 驱动程序初始化或安装细节,而是着重讨论为初始化定制并与硬件接口的驱动程序各片断。在高级IDE驱动程序初始化过程中,以下函数被用于设置IDE控制器:

static __inline__ void ide_init_default_hwifs(void)

该函数在文件中被定义为:include/asm/mach/ide.h,为非标准IDE控制器配置硬件接口数据结构,注册高级IDE驱动程序EP9312 IDE接口,并为接口设置IRQ。

在结构特殊初始化代码内完成的IRQ设置仅仅在硬件接口数据结构内设置IDE接口所需的平台IRQ号码。调用request_irq() 由高级IDE驱动程序负责。

IDE硬件接口数据结构通过调用以下函数得到配置,并同时在include/asm/mach/ide.h内得到定义:

static __inline__ void ide_init_hwif_ports(hw_regs_t *hw, int data_port, int ctrl_port, int *irq)

该函数通过设置硬件接口数据结构内的命令和控制注册地址配置了非标准EP9312 IDE 接口,并设置和实现EP9312上的接口。

在ide_init_default_hwifs(void) 函数设置IDE控制器并由高级IDE驱动程序注册硬件接口后,结构特殊接口通过以下函数调用得到进一步初始化:

void ep93xx_ide_init(unsigned int * pointer)

该函数在文档驱动器/ide/ide-ep93xx.c 内被定义,并同时执行几个任务--把结构特殊函数映射到硬件接口数据结构内的函数指针函数,如果平台设有DMA则设置DMA接口。

IDE硬件接口数据结构的结构特殊函数指针如下所示:

typedef struct hwif_s {…ide_rw_proc_t *rwproc; ide_ideproc_t *ideproc; ide_dmaproc_t *dmaproc; … } ide_hwif_t;

ideproc 处理PIO模式转换,并被映射到结构特殊函数 ep93xx_ideproc()。rwproc 和dmaproc 都处理DMA模式转换。rwproc 向ep93xx_rwproc()映射,dmaproc向ep93xx_dmaproc()映射。高级IDE驱动程序检测这些指针是否无效。如果确为无效,则放弃结构特殊函数而采用默认函数。ideproc()和dmaproc()均系基于IOCTL的函数,可执行一系列高级IDE驱动程序定义的ioctls命令。rwproc()函数为特殊转换速度和方向设置IDE控制器。这些EP9312结构特殊函数都在文件驱动程序/ide/ide-ep93xx.c内得到定义。函数原型示意如下:

static void ep93xx_ideproc(ide_ide_action_t action, ide_drive_t * drive, void * buffer, unsigned int count) static void ep93xx_dmaproc(ide_dma_action_t action, ide_drive_t *drive) static void ep93xx_rwproc(ide_drive_t * drive, ide_dma_action_t action)

此外,一部分结构特殊执行命令也是几个IDE普通宏命令的再定义。它们是直接读写IDE设备的宏命令。文件 /include/asm/mach/ide.h 下的宏映射到EP9312 定义。

#define OUT_BYTE(b, p) ep93xx_ide_outb((b), (p)) #define OUT_WORD(w, p) ep93xx_ide_outw((w), (p)) #define IN_BYTE(p) ep93xx_ide_inb((p)) #define IN_WORD(p) ep93xx_ide_inw((p))

硬件接口(EP9312 IDE控制器接口)被初始化并与高级IDE驱动程序一起注册后,高级IDE驱动程序通过探测相连的IDE设备硬件接口继续初始化。如果设备被探测到,则与操作系统一起注册。设备与操作系统一起注册后,向能在设备上执行的操作表上映射。这样,操作系统也获得了设备的额外信息,并需要对设备进行资源管理。这些额外信息包括大小和分区数量等。以下是注册IDE硬盘的函数调用:

register_disk(struct gendisk *gd, int drive,

unsigned minors,

struct block_device_operations *ops,

long size)

高级IDE驱动程序用探测设备时获得的的函数参数值调用这个函数。第一个参数是gd,它是描述盘片布局的数据结构。第二个参数--drive,是设备编号。对于EP9312而言,设备编号或为0,或为1,因为硬件只支持的两台设备。第三个参数--minors,是设备被探测时发现的盘片分区。第四个参数--block_device_operations,是函数指针列表,系IDE驱动程序硬盘执行所定义。被映射到该列表中的函数采用结构特殊函数执行不同任务。最后一个参数--size,是指设备的扇区数,它同样也是从设备中直接获得。

设备指针列表包括以下区块设备操作:

open - 设备和驱动程序实例初始化

release - 关闭设备或清除驱动程序实例

ioctl - 填补空白,是通过内核向设备驱动程序传递的一种信息的一种方式

check media change - 处理支持可移动媒体的设备

revalidate - 处理支持可移动媒体的设备(通常为设备指定)

区块设备的设备操作列表不包括任何输入输出操作。对于区块设备而言,request方法用于处理设备输入输出,并与等待的输入输出操作队列相关,因此进一步与字符设备有所区分。Request方法和队列均由高级IDE设备驱动器定义,与操作系统一起注册并与设备主要编号相连。

除了将设备和操作系统一起注册,高级IDE设备驱动程序还通过数据结构在本地管理该设备,数据结构包括映射到IDE设备特别函数的函数指针。下面是映射到针对IDE硬盘函数的该数据结构的一部分:

static ide_driver_t idedisk_driver = {

cleanup: idedisk_cleanup,

standby: do_idedisk_standby,

flushcache: do_idedisk_flushcache,

do_request: do_rw_disk,

end_request: NULL,

ioctl: NULL,

open: idedisk_open,

release: idedisk_release,

media_change: idedisk_media_change,

revalidate: idedisk_revalidate,

pre_reset: idedisk_pre_reset,

capacity: idedisk_capacity,

special: idedisk_special,

proc: idedisk_proc,

reinit: idedisk_reinit,

};

值得注意的是,一些函数指针直接向与操作系统一起注册的文件操作列表函数指针映射,而此时IDE设备驱动器内部使用其他函数指针。例如,高级设备驱动程序内部使用函数指针do_request 和 end_request处理要求方法输入输出。

这就涵盖了IDE设备驱动器的结构特殊API。下一步是创建文档系统特殊文件,从而帮助用户级应用进入该设备。使用以下命令生成IDE硬盘驱动特殊文件:mknod /dev/hda1 b 3 1

正如在触摸屏特殊文件创建中谈及,可在系统初始化阶段安排自动执行该步骤,或者用户可以在系统启动运行显示操作提示时手工操作该命令。

用户级应用较少直接调用区块设备。一般而言,区块设备直接通过内核级文档系统执行接入。用户级应用通常获取具有操作系统实用程序的区块设备,以执行文档系统创建、安装访问文档系统的设备等文档系统操作。命令行工具涵盖分割、格式化、安装和验证区块设备。例如,以下是用mnknod命令创建的设备的一个安装设备命令:

mount -t ext3 -o rw /dev/hda1 /mnt/drive

-t ext3 指出设备由一个Extended 3文档系统配置;-o rw 则说明设备应该具备读写函数;/dev/hda1是被安装设备的文档系统特殊文件;/mnt/drive 则是用户获取设备所存文档系统内容的安装位置。

添加Linux内核的新设备驱动程序支持

Linux内核用以下三个命令建立:

make menuconfig (config, xconfig, oldconfig, etc.)

make dep

make

首先,Linux内核针对目标运行环境进行配置。用户还可选择添加支持各种设备、支持各种文档系统和配置引导参数等。当一个新的设备驱动程序在Linux内核中得到执行时,必须增加对该新设备的配置支持,所以要先更新驱动程序目录中合适设备类型子目录下的Makefile。在Makefile中,必须增加新选项建立设备驱动程序二进制文件,并且直接与Linux内核相连或创建一个模块。第二步需要更新驱动程序目录设备类型子目录下的Config. in。此新设备的配置选项必须加入Config.in。

小结

本文无意阐述Linux设备驱动程序的各个环节,因为包括Linux源代码在内的各种资源都已对此做出了解释。相反,本文旨在探索针对嵌入式非标准设备、用以执行设备驱动程序的硬件API。对于几个不同类型的设备驱动程序,本文以EP9312片上系统平台为例,详解了这些为硬件接口定制的API。了解如何设计并执行这些API是为新设备编写驱动程序的第一步。

篇6:WIN技巧:WIN操作系统中如何备份驱动程序

虽然,我们可以在安装Windows之后,从各硬件厂商的 下载硬件驱动,但费时 费力;而且如果你安装系统之前没有备份网卡的驱动程序,则重装系统之后根本不能上网,何谈下载硬件驱动程序!所以,借助相应的工具,将需要的硬件驱动程序从老系统中剥离出来,重装系统后再恢复驱动程序,这才是王道……

PCD工具谱

软件名称:驱动精灵

软件版本: Build 3.11

授权方式:共享软件(15天全功能试用)

软件大小:987KB

下载网址:www.mydown.com

目前可供选择的驱动程序备份/恢复软件有很多,既有国外的Driver Magician,也有国内的《驱动备份精灵》,它们大都操作简单、方便,但经过笔者的长期使用,发现它们都不尽如人意,算起来还是老牌的《驱动精灵》兼容性最好。

收集驱动程序

在《驱动精灵》的主界面右侧有一列功能按钮,分别用来完成不同的功能(如图1),

图1◎《驱动精灵》可以快速搜集旧系统的驱动程序并进行备份

快速收集:只收集发布商非微软的驱动程序。

收集全部:收集系统中所有硬件的驱动程序,包括Windows内置的发布商为微软的驱动程序。

备份驱动:备份选中的驱动程序。

全部备份:备份系统中所有硬件的驱动程序。

恢复驱动:顾名思义,就是在新系统中恢复原来安装的驱动程序。

因为Windows内置的驱动程序一般都经过微软签名,并且发布商为Microsoft,因此未签名并且发布商不是Microsoft的驱动程序,有可能不是Windows安装光盘中包含的,这些驱动正是我们需要备份的。点击“快速收集”按钮,让《驱动精灵》收集当前系统中安装的没有经过微软签名和发布商不是微软的驱动程序。

收集驱动程序后,《驱动精灵》会显示所有驱动程序列表,点击某个驱动程序,还会在下方显示该驱动程序的相关信息。

篇7:Windows设备驱动程序的研制开发

Windows设备驱动程序的研制开发

引言:

由于工作关系,我经常涉及PC机与外围设备接口的工作,从PC机这方面要做的工作看来,主要是通过接口处理外围设备的中断,通过I/O端口或内存地址与外设互相传递数据。从计算机原理的角度看,所要达到的目的很简单,那么如何编写程序完成上述功能呢?

目前国内流行的PC操作系统有三种:DOS,Win95/98系列,WindowsNT。DOS是单用户、单任务操作系统,由于PC机硬件处理速度不断提高,基于单用户、单任务的操作系统越来越不能充分发挥硬件的功能,现在只应用于一些老式PC及其它个别场合,有逐渐被淘汰的趋势;Win95/98系列和WindowsNT属于多任务操作系统,不论从其原理还是界面上看,这两种操作系统都比DOS有着无可比拟的优越性,这两种操作系统虽然在界面和操作上及其相似,但其内部实现的诸多方面有许多区别,有些区别是本质上的。Win95/98设计目标是针对一般家庭用户,安全性及可靠性存在许多薄弱环节,就可靠性而言,Win95/98系列不能很好的防止多任务环境中某个进程的非法操作导致系统中其它程序甚至整个系统的'崩溃,而WindowsNT在这方面及其它诸多方面设计的相当严谨。这两种操作系统是Microsoft公司同一时期的产品,但针对不同的使用群,所以在一些重要场合及生产实践中应该选择WindowsNT作为计算机的操作系统,此外,从发展趋势来看,WindowsNT已经成为定型产品,具有相对稳定性。

在不同操作系统下编写驱动程序是有很大区别的,在DOS平台上,应用程序和设备驱动程序之间没有标准的接口,它们在外部表现为一个扩展名为EXE的文件,驱动程序的作用被柔和在应用程序中,这样,应用程序为了使用不同厂商的同一类设备,必须了解这些设备在接口上具体的硬件实现,同时,对于一个特定型号的硬件产品,所有支持它的应用软件中对于控制整个设备动作的这部分代码,可能被多次重写。这种情况不适应硬件及应用软件的飞速发展。Windows系统在这方面,进行了根本性改进,把控制设备动作的这部分代码独立出来,提出了设备驱动程序的概念,驱动程序是应用程序和硬件设备之间的一个桥梁,应用程序与驱动程序之间有明确的接口,应用程序通过与驱动程序交换信息,达到控制外设的目的。接口定义的操作是面向设备的,这就是说,在应用程序的设计中,并不用关心对外设操作的具体硬件实现,只是对驱动程序发出一系列指令既可;驱动程序接受来自上层应用程序的指示,具体操纵实际硬件,完成用户功能。具体实现上,Win95/98系列与WindowsNT又有所区别,WindowsNT是严格按照上述思路设计的;而Win95/98系列不那么严格,其支持上述思路,但同时应用程序也可以绕过驱动程序直接访问实际物理I/O,这样做,增加程序设计的灵活性,但同时,对系统可靠性造成一定隐患。这也正是Win95/98系列可靠性低于WinNT的原因之一。

表1-1 三种操作系统下访问接口比较

操作系统应用程序访问接口方式访问权限DOS直接访问所有[注]Windows95/98通过设备驱动程序*.VXD所有[注]直接访问仅I/O端口WindowsNT

[1] [2] [3] [4] [5]

篇8:Linux字符设备驱动程序解析

Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得linux的设备操作犹如文件一般,在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open()、close()、read()、write() 等。

Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。

下面我们来假设一个非常简单的虚拟字符设备:这个设备中只有一个4个字节的全局变量int global_var,而这个设备的名字叫做“globalvar”。对“globalvar”设备的读写等操作即是对其中全局变量global_var的操作。

驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备:

static int __init globalvar_init(void){ if (register_chrdev(MAJOR_NUM, “ globalvar ”, &gobalvar_fops)) { //…注册失败 } else { //…注册成功 }}

其中,register_chrdev函数中的参数MAJOR_NUM为主设备号, “globalvar”为设备名,globalvar_fops为包含基本函数入口点的结构体,类型为file_operations。当globalvar模块被加载时,globalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。

与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的“反函数”

unregister_chrdev():static void __exit globalvar_exit(void){ if (unregister_chrdev(MAJOR_NUM, “ globalvar ”)) { //…卸载失败 } else { //…卸载成功 }}

随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其中的一部分。对于字符设备来说,要提供的主要入口有:open()、release()、read()、write()、ioctl()、llseek()、poll()等。

open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open() 函数:

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

其中参数inode为设备特殊文件的inode (索引结点) 结构的指针,参数file是指向这一设备的文件结构的指针。open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用 MINOR(inode->i - rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误)等;

release()函数 当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release() 函数:

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

release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。

read()函数 当对设备特殊文件进行read() 系统调用时,将调用驱动程序read()函数:

ssize_t (*read) (struct file *, char *, size_t, loff_t *);

用来从设备中读取数据。当该函数指针被赋为NULL 值时,将导致read 系统调用出错并返回-EINVAL(“Invalid argument,非法参数”)。函数返回非负值表示成功读取的字节数(返回值为“signed size”数据类型,通常就是目标平台上的固有整数类型)。

globalvar_read函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off){ … copy_to_user(buf, &global_var, sizeof(int)); …}

write( ) 函数 当设备特殊文件进行write () 系统调用时,将调用驱动程序的write () 函数:

ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

向设备发送数据。如果没有这个函数,write 系统调用会向调用程序返回一个-EINVAL。如果返回值非负,则表示成功写入的字节数。

globalvar_write函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off){…copy_from_user(&global_var, buf, sizeof(int));…}

ioctl() 函数 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:

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

unsigned int参数为设备驱动程序要执行的命令的代码,由用户自定义,unsigned long参数为相应的命令提供参数,类型可以是整型、指针等。如果设备不提供ioctl 入口点,则对于任何内核未预先定义的请求,ioctl 系统调用将返回错误(-ENOTTY,“No such ioctl fordevice,该设备无此ioctl 命令”)。如果该设备方法返回一个非负值,那么该值会被返回给调用程序以表示调用成功。

llseek()函数 该函数用来修改文件的当前读写位置,并将新位置作为(正的)返回值返回,原型为:

loff_t (*llseek) (struct file *, loff_t, int);

poll()函数 poll 方法是poll 和select 这两个系统调用的后端实现,用来查询设备是否可读或可写,或是否处于某种特殊状态,原型为:

unsigned int (*poll) (struct file *, struct poll_table_struct *);

我们将在“设备的阻塞与非阻塞操作”一节对该函数进行更深入的介绍,

设备“gobalvar”的驱动程序的这些函数应分别命名为gobalvar_open、gobalvar_ release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此设备“gobalvar”的基本入口点结构变量gobalvar_fops 赋值如下:

struct file_operations gobalvar_fops = { read: gobalvar_read, write: gobalvar_write,};

上述代码中对gobalvar_fops的初始化方法并不是标准C所支持的,属于GNU扩展语法。

完整的globalvar.c文件源代码如下:

#include #include #include #include MODULE_LICENSE(“GPL”);#define MAJOR_NUM 254 //主设备号static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);//初始化字符设备驱动的file_operations结构体struct file_operations globalvar_fops ={ read: globalvar_read, write: globalvar_write,};static int global_var = 0; //“globalvar”设备的全局变量static int __init globalvar_init(void){ int ret; //注册设备驱动 ret = register_chrdev(MAJOR_NUM, “globalvar”, &globalvar_fops); if (ret) { printk(“globalvar register failure”); } else { printk(“globalvar register success”); } return ret;}static void __exit globalvar_exit(void){ int ret; //注销设备驱动 ret = unregister_chrdev(MAJOR_NUM, “globalvar”); if (ret) { printk(“globalvar unregister failure”); } else { printk(“globalvar unregister success”); }}static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off){ //将global_var从内核空间复制到用户空间 if (copy_to_user(buf, &global_var, sizeof(int))) { return - EFAULT; } return sizeof(int);}static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off){ //将用户空间的数据复制到内核空间的global_var if (copy_from_user(&global_var, buf, sizeof(int))) { return - EFAULT; } return sizeof(int);}module_init(globalvar_init);module_exit(globalvar_exit); 运行:gcc -D__KERNEL__ -DMODULE -DLINUX -I /usr/local/src/linux2.4/include -c -o globalvar.o globalvar.c 编译代码,运行:inmod globalvar.o 加载globalvar模块,再运行:cat /proc/devices 发现其中多出了“254 globalvar”一行,如下图: 接着我们可以运行:mknod /dev/globalvar c 254 0 创建设备节点,用户进程通过/dev/globalvar这个路径就可以访问到这个全局变量虚拟设备了。我们写一个用户态的程序globalvartest.c来验证上述设备:#include #include #include #include main(){ int fd, num; //打开“/dev/globalvar” fd = open(“/dev/globalvar”, O_RDWR, S_IRUSR | S_IWUSR); if (fd != -1 ) { //初次读globalvar read(fd, &num, sizeof(int)); printf(“The globalvar is %d\\n”, num); //写globalvar printf(“Please input the num written to globalvar\\n”); scanf(“%d”, &num); write(fd, &num, sizeof(int)); //再次读globalvar read(fd, &num, sizeof(int)); printf(“The globalvar is %d\\n”, num); //关闭“/dev/globalvar” close(fd); } else { printf(“Device open failure\\n”); }} 编译上述文件:gcc -o globalvartest.o globalvartest.c 运行./globalvartest.o 可以发现“globalvar”设备可以正确的读写。

Windows系统未正确安装新的设备驱动程序后,开机总是出现“欢迎

下载编写Linux操作系统设备驱动程序概述(共8篇)
编写Linux操作系统设备驱动程序概述.doc
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档
最新范文更多
    热门文章
      猜你喜欢
      点击下载本文文档