当前位置:首页 » 操作系统 » Linux设备驱动宋

Linux设备驱动宋

发布时间: 2023-02-03 09:44:24

1. linux的设备驱动一般分为几类各有什么特点

大致分为三类,字符驱动,块设备驱动,网络设备驱动。
字符设备可以看成是用字节流存取的文件

块设备则可以看成是可以任意存取字节数的字符设备,在应用上只是内核管理数据方式不同

网络设备可以是一个硬件设备,或者是软件设备,他没有相应的read write,它是面向流的一种特殊设备。

2. 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.//hehe

(请看下节,实例剖析)

读/写时,它首先察看缓冲区的内容,如果缓冲区的数据

如何编写Linux***作系统下的设备驱动程序

Roy G

二.实例剖析

我们来写一个最简单的字符设备驱动程序.虽然它什么也不做,但是通过它

可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会

获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel

上可能会出现问题,我还没测试过.//xixi

#define __NO_VERSION__

#include

#include

char kernel_version [] = UTS_RELEASE;

这一段定义了一些版本信息,虽然用处不是很大,但也必不可少.Johnsonm说所

有的驱动程序的开头都要包含,但我看倒是未必.

由于用户进程是通过设备文件同硬件打交道,对设备文件的***作方式不外乎就

是一些系统调用,如 open,read,write,close...., 注意,不是fopen, fread.,

但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据

结构:

struct file_operations {

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

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

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

int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);

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

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

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

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

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

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

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

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

int (*revalidate) (dev_t dev);

}

这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用

在对设备文件进行诸如read/write***作时,系统调用通过设备文件的主设备号

找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制

权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样,则编写

设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域.

相当简单,不是吗?

下面就开始写子程序.

#include

#include

#include

#include

#include

unsigned int test_major = 0;

static int read_test(struct inode *node,struct file *file,

char *buf,int count)

{

int left;

if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )

return -EFAULT;

for(left = count left > 0 left--)

{

__put_user(1,buf,1);

buf++;

}

return count;

}

这个函数是为read调用准备的.当调用read时,read_test()被调用,它把用户的

缓冲区全部写1.

buf 是read调用的一个参数.它是用户进程空间的一个地址.但是在read_test

被调用时,系统进入核心态.所以不能使用buf这个地址,必须用__put_user(),

这是kernel提供的一个函数,用于向用户传送数据.另外还有很多类似功能的

函数.请参考.在向用户空间拷贝数据之前,必须验证buf是否可用.

这就用到函数verify_area.

static int write_tibet(struct inode *inode,struct file *file,

const char *buf,int count)

{

return count;

}

static int open_tibet(struct inode *inode,struct file *file )

{

MOD_INC_USE_COUNT;

return 0;

} static void release_tibet(struct inode *inode,struct file *file )

{

MOD_DEC_USE_COUNT;

}

这几个函数都是空***作.实际调用发生时什么也不做,他们仅仅为下面的结构

提供函数指针。

struct file_operations test_fops = {

NULL,

read_test,

write_test,

NULL, /* test_readdir */

NULL,

NULL, /* test_ioctl */

NULL, /* test_mmap */

open_test,

release_test, NULL, /* test_fsync */

NULL, /* test_fasync */

/* nothing more, fill with NULLs */

};

设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序

可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(moles),

如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能

动态的卸载,不利于调试,所以推荐使用模块方式。

int init_mole(void)

{

int result;

result = register_chrdev(0, "test", &test_fops);

if (result < 0) {

printk(KERN_INFO "test: can't get major number ");

return result;

}

if (test_major == 0) test_major = result; /* dynamic */

return 0;

}

在用insmod命令将编译好的模块调入内存时,init_mole 函数被调用。在

这里,init_mole只做了一件事,就是向系统的字符设备表登记了一个字符

设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是

零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,

参数三用来登记驱动程序实际执行***作的函数的指针。

如果登记成功,返回设备的主设备号,不成功,返回一个负值。

void cleanup_mole(void)

{

unregister_chrdev(test_major, "test");

}

在用rmmod卸载模块时,cleanup_mole函数被调用,它释放字符设备test

在系统字符设备表中占有的表项。

一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。

下面编译

$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c

得到文件test.o就是一个设备驱动程序。

如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后

ld -r file1.o file2.o -o molename.

驱动程序已经编译好了,现在把它安装到系统中去。

$ insmod -f test.o

如果安装成功,在/proc/devices文件中就可以看到设备test,

并可以看到它的主设备号,。

要卸载的话,运行

$ rmmod test

下一步要创建设备文件。

mknod /dev/test c major minor

c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。

用shell命令

$ cat /proc/devices | awk "\$2=="test" {print \$1}"

就可以获得主设备号,可以把上面的命令行加入你的shell script中去。

minor是从设备号,设置成0就可以了。

我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。

#include

#include

#include

#include

main()

{

int testdev;

int i;

char buf[10];

testdev = open("/dev/test",O_RDWR);

if ( testdev == -1 )

{

printf("Cann't open file ");

exit(0);

}

read(testdev,buf,10);

for (i = 0; i < 10;i++)

printf("%d ",buf);

close(testdev);

}

编译运行,看看是不是打印出全1 ?

以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,

DMA,I/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。

如何编写Linux***作系统下的设备驱动程序

Roy G

三 设备驱动程序中的一些具体问题。

1. I/O Port.

和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,

在linux下,***作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可以

对任意的I/O口***作,这样就很容易引起混乱。每个驱动程序应该自己避免

误用端口。

有两个重要的kernel函数可以保证驱动程序做到这一点。

1)check_region(int io_port, int off_set)

这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。

参数1:io端口的基地址,

参数2:io端口占用的范围。

返回值:0 没有占用, 非0,已经被占用。

2)request_region(int io_port, int off_set,char *devname)

如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用

之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports

文件中可以看到你登记的io口。

参数1:io端口的基地址。

参数2:io端口占用的范围。

参数3:使用这段io地址的设备名。

在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。

在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当

于访问一段内存。经常性的,我们要获得一块内存的物理地址。在dos环境下,

(之所以不说是dos***作系统是因为我认为DOS根本就不是一个***作系统,它实

在是太简单,太不安全了)只要用段:偏移就可以了。在window95中,95ddk

提供了一个vmm 调用 _MapLinearToPhys,用以把线性地址转化为物理地址。但

在Linux中是怎样做的呢?

2 内存***作

在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用

get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,

kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!关于

kmalloc返回的是物理地址这一点本人有点不太明白:既然从线性地址到物理

地址的转换是由386cpu硬件完成的,那样汇编指令的***作数应该是线性地址,

驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc

返回的确实是物理地址,而且也可以直接通过它访问实际的RAM,我想这样可

以由两种解释,一种是在核心态禁止分页,但是这好像不太现实;另一种是

linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法

不知对不对,还请高手指教。

言归正传,要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符

结构占用了。kmalloc用法参见khg.

内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000

以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得

重新映射以后的地址。

另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直

驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。

这可以通过牺牲一些系统内存的方法来解决。

具体做法是:比如说你的机器由32M的内存,在lilo.conf的启动参数中加上

mem=30M,这样linux就认为你的机器只有30M的内存,剩下的2M内存在vremap

之后就可以为DMA所用了。

请记住,用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。

3 中断处理

同处理I/O端口一样,要使用一个中断,必须先向系统登记。

int request_irq(unsigned int irq ,

void(*handle)(int,void *,struct pt_regs *),

unsigned int long flags,

const char *device);

irq: 是要申请的中断。

handle:中断处理函数指针。

flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。

device:设备名。

如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的

中断。

4一些常见的问题。

对硬件***作,有时时序很重要。但是如果用C语言写一些低级的硬件***作

的话,gcc往往会对你的程序进行优化,这样时序就错掉了。如果用汇编写呢,

gcc同样会对汇编代码进行优化,除非你用volatile关键字修饰。最保险的

办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码

都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要

用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现

出来。

关于kernel的调试工具,我现在还没有发现有合适的。有谁知道请告诉我,

不胜感激。我一直都在printk打印调试信息,倒也还凑合。

关于设备驱动程序还有很多内容,如等待/唤醒机制,块设备的编写等。

我还不是很明白,不敢乱说。

3. linux下的设备驱动

你好,解决驱动问题,最简单最容易最有效的详细方法就是:
直接到www.drivergenius.com下载“驱动精灵”软件,一定要是最新的,旧版的“驱动精灵”不好用,安装好它后,双击打开--驱动更新--自动安装--开始更新,它会自动在网上搜索最新的最适合你电脑的所有驱动,它会自动下载并自动完整安装好,根本不用你动手。
这个软件的最大优点就是:如果你电脑缺少什么驱动它会一一帮你装上最适合的,如果你电脑驱动残旧问题多多它会自动更新好并自动修复。是不是非常非常的方便,推荐不怎么懂电脑的人使用这个方法。
如果还不行,一定要先采纳我的答案再+我314345040,因为这样电脑会及时提示我,什么问题被采纳了,你具体的问题情况我也能及时打开网页了解到,要相信你的电脑问题我会尽全力帮你解决的。g

4. LINUX 终端设备驱动

在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty (Teletype)来简称各种类型的终端设备。对于嵌入式系统而言,最普遍采用的是UART (Universal Asynchronous Receiver/Transmitter)串行端口,日常生活中简称串口。
Linux内核中tty的层次结构它包含tty核心tty_10.c、tty或路规在n_tty.C(头现N_11Y线路规程)和tty驱动实例xxx_tty.c,tty线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式tty _io.c本身是一个标准的字符设备驱动,它对上有字符改备的职贡,买现tle_operatIonS双贝图效。但是tty核心层对下又定义了tty_driver的架构,这样tty设备驱动的主体工作就变成了琪允tty_driVeT依构体中的成员,实现其中的tty_operations的成员函数,而不再是去实现file_operations这一级的工作。tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。接收数据的流程为:从tty硬件接收到的数据向上交给tty驱动,接着进入tty线路规程驱动,再进入tty核心,在这里它被一个用户获取。尽管一个特定的底层UART设备驱动完全可以遵循上述tty_driver的方法来设计,即定义tty_driver并实现tty_operations中的成员函数,但是鉴于串口之间的共性,Linux考虑在文件drivers'ttyliserial'serial_core.c中实现了UART设备的通用tty驱动层(我们可以称其为串口核心层)。这样,UART驱动的主要任务就进一步演变成了实现serial-core.c中定义的一组uart_xxx接口而不是tty_xxx接口。因此,按照面向对象的思想,可以认为tty_driver是字符设备的泛化、serial-core是tty_driver的泛化,而具体的串口驱动又是serial-core的泛化。

5. linux驱动程序结构框架及工作原理分别是什么

一、Linux device driver 的概念x0dx0ax0dx0a系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:x0dx0ax0dx0a1、对设备初始化和释放;x0dx0ax0dx0a2、把数据从内核传送到硬件和从硬件读取数据;x0dx0ax0dx0a3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;x0dx0ax0dx0a4、检测和处理设备出现的错误。x0dx0ax0dx0a在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。x0dx0ax0dx0a已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。x0dx0ax0dx0a最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。x0dx0ax0dx0a二、实例剖析x0dx0ax0dx0a我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。x0dx0ax0dx0a由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close?, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:x0dx0ax0dx0aSTruct file_operatiONs {x0dx0ax0dx0aint (*seek) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*read) (struct inode * ,struct file *, char ,int);x0dx0ax0dx0aint (*write) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*readdir) (struct inode * ,struct file *, struct dirent * ,int);x0dx0ax0dx0aint (*select) (struct inode * ,struct file *, int ,select_table *);x0dx0ax0dx0aint (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);x0dx0ax0dx0aint (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);x0dx0ax0dx0aint (*open) (struct inode * ,struct file *);x0dx0ax0dx0aint (*release) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fsync) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fasync) (struct inode * ,struct file *,int);x0dx0ax0dx0aint (*check_media_change) (struct inode * ,struct file *);x0dx0ax0dx0aint (*revalidate) (dev_t dev);x0dx0ax0dx0a}x0dx0ax0dx0a这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。x0dx0ax0dx0a下面就开始写子程序。x0dx0ax0dx0a#include 基本的类型定义x0dx0ax0dx0a#include 文件系统使用相关的头文件x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0aunsigned int test_major = 0;x0dx0ax0dx0astatic int read_test(struct inode *inode,struct file *file,char *buf,int count)x0dx0ax0dx0a{x0dx0ax0dx0aint left; 用户空间和内核空间x0dx0ax0dx0aif (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )x0dx0ax0dx0areturn -EFAULT;x0dx0ax0dx0afor(left = count ; left > 0 ; left--)x0dx0ax0dx0a{x0dx0ax0dx0a__put_user(1,buf,1);x0dx0ax0dx0abuf++;x0dx0ax0dx0a}x0dx0ax0dx0areturn count;x0dx0ax0dx0a}x0dx0ax0dx0a这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。x0dx0ax0dx0astatic int write_test(struct inode *inode,struct file *file,const char *buf,int count)x0dx0ax0dx0a{x0dx0ax0dx0areturn count;x0dx0ax0dx0a}x0dx0ax0dx0astatic int open_test(struct inode *inode,struct file *file )x0dx0ax0dx0a{x0dx0ax0dx0aMOD_INC_USE_COUNT; 模块计数加以,表示当前内核有个设备加载内核当中去x0dx0ax0dx0areturn 0;x0dx0ax0dx0a}x0dx0ax0dx0astatic void release_test(struct inode *inode,struct file *file )x0dx0ax0dx0a{x0dx0ax0dx0aMOD_DEC_USE_COUNT;x0dx0ax0dx0a}x0dx0ax0dx0a这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。x0dx0ax0dx0astruct file_operations test_fops = {?x0dx0ax0dx0aread_test,x0dx0ax0dx0awrite_test,x0dx0ax0dx0aopen_test,x0dx0ax0dx0arelease_test,x0dx0ax0dx0a};x0dx0ax0dx0a设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(moles),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。x0dx0ax0dx0aint init_mole(void)x0dx0ax0dx0a{x0dx0ax0dx0aint result;x0dx0ax0dx0aresult = register_chrdev(0, "test", &test_fops); 对设备操作的整个接口x0dx0ax0dx0aif (result < 0) {x0dx0ax0dx0aprintk(KERN_INFO "test: can't get major number\n");x0dx0ax0dx0areturn result;x0dx0ax0dx0a}x0dx0ax0dx0aif (test_major == 0) test_major = result; /* dynamic */x0dx0ax0dx0areturn 0;x0dx0ax0dx0a}x0dx0ax0dx0a在用insmod命令将编译好的模块调入内存时,init_mole 函数被调用。在这里,init_mole只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。x0dx0ax0dx0a如果登记成功,返回设备的主设备号,不成功,返回一个负值。x0dx0ax0dx0avoid cleanup_mole(void)x0dx0ax0dx0a{x0dx0ax0dx0aunregister_chrdev(test_major,"test");x0dx0ax0dx0a}x0dx0ax0dx0a在用rmmod卸载模块时,cleanup_mole函数被调用,它释放字符设备test在系统字符设备表中占有的表项。x0dx0ax0dx0a一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。x0dx0ax0dx0a下面编译 :x0dx0ax0dx0a$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c _c表示输出制定名,自动生成.o文件x0dx0ax0dx0a得到文件test.o就是一个设备驱动程序。x0dx0ax0dx0a如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后x0dx0ax0dx0ald ?-r ?file1.o ?file2.o ?-o ?molename。x0dx0ax0dx0a驱动程序已经编译好了,现在把它安装到系统中去。x0dx0ax0dx0a$ insmod ?_f ?test.ox0dx0ax0dx0a如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。要卸载的话,运行 :x0dx0ax0dx0a$ rmmod testx0dx0ax0dx0a下一步要创建设备文件。x0dx0ax0dx0amknod /dev/test c major minorx0dx0ax0dx0ac 是指字符设备,major是主设备号,就是在/proc/devices里看到的。x0dx0ax0dx0a用shell命令x0dx0ax0dx0a$ cat /proc/devicesx0dx0ax0dx0a就可以获得主设备号,可以把上面的命令行加入你的shell script中去。x0dx0ax0dx0aminor是从设备号,设置成0就可以了。x0dx0ax0dx0a我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0amain()x0dx0ax0dx0a{x0dx0ax0dx0aint testdev;x0dx0ax0dx0aint i;x0dx0ax0dx0achar buf[10];x0dx0ax0dx0atestdev = open("/dev/test",O_RDWR);x0dx0ax0dx0aif ( testdev == -1 )x0dx0ax0dx0a{x0dx0ax0dx0aprintf("Cann't open file \n");x0dx0ax0dx0aexit(0);x0dx0ax0dx0a}x0dx0ax0dx0aread(testdev,buf,10);x0dx0ax0dx0afor (i = 0; i < 10;i++)x0dx0ax0dx0aprintf("%d\n",buf[i]);x0dx0ax0dx0aclose(testdev);x0dx0ax0dx0a}x0dx0ax0dx0a编译运行,看看是不是打印出全1 x0dx0ax0dx0a以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。上述给出了一个简单的字符设备驱动编写的框架和原理,更为复杂的编写需要去认真研究LINUX内核的运行机制和具体的设备运行的机制等等。希望大家好好掌握LINUX设备驱动程序编写的方法。

6. Linux字符设备驱动的组成

在Linux中,字符设备驱动由如下几个部分组成。
1.字符设备驱动模块加载与卸载函数
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号
的释放和cdev的注销。
Linux内核的编码习惯是为设备定义一个设备相关的结构体,该结构体包含设备所涉及的cdev、私有
数据及锁等信息。2.字符设备驱动的file_operations结构体中的成员函数
file_operations结构体中的成员函数是字符设备驱动与内核虚拟文件系统的接口,是用户空间对Linux
进行系统调用最终的落实者。设备驱动的读函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不宜直
接读写,count是要读的字节数,f_pos是读的位置相对于文件开头的偏移。
设备驱动的写函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不宜直
接读写,count是要写的字节数,f_pos是写的位置相对于文件开头的偏移。
由于用户空间不能直接访问内核空间的内存,因此借助了函数_from_user()完成用户空间缓冲
区到内核空间的复制,以及_to_user()完成内核空间到用户空间缓冲区的复制,见代码第6行和第14
行。
完成内核空间和用户空间内存复制的_from_user()和_to_user()的原型分别为:
unsigned long _from_user(void *to, const void _ _user *from, unsigned long count);
unsigned long _to_user(void _ _user *to, const void *from, unsigned long count);
上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。如果复制失败,则返
回负值。如果要复制的内存是简单类型,如char、int、long等,则可以使用简单的put_user()和
get_user()读和写函数中的_user是一个宏,表明其后的指针指向用户空间,实际上更多地充当了代码自注释的
功能。内核空间虽然可以访问用户空间的缓冲区,但是在访问之前,一般需要先检查其合法性,通过
access_ok(type,addr,size)进行判断,以确定传入的缓冲区的确属于用户空间。

7. 怎样写linux下的USB设备驱动程序

USB驱动程序基础
在动手写USB驱动程序这前,让我们先看看写的USB驱动程序在内核中的结构,如下图:



USB通信最基本的形式是通过端点(USB端点分中断、批量、等时、控制四种,每种用途不同),USB端点只能往一个方向传送数据,从主机到设备或者从设备到主机,端点可以看作是单向的管道(pipe)。所以我们可以这样认为:设备通常具有一个或者更多的配置,配置经常具有一个或者更多的接口,接口通常具有一个或者更多的设置,接口没有或具有一个以上的端点。驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否已经安装了硬件。USB核心使用一个列表(是一个包含制造商ID和设备号ID的一个结构体)来判断对于一个设备该使用哪一个驱动程序,热插拨脚本使用它来确定当一个特定的设备插入到系统时该自动装载哪一个驱动程序。
上面我们简要说明了驱动程序的基本理论,在写一个设备驱动程序之前,我们还要了解以下两个概念:模块和设备文件。
模块:是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是可以装载到系统中作为内核的一部分运行,从而可以动态扩充内核的功能。模块最主要的用处就是用来实现设备驱动程序。Linux下对于一个硬件的驱动,可以有两种方式:直接加载到内核代码中,启动内核时就会驱动此硬件设备。另一种就是以模块方式,编译生成一个.ko文件(在2.4以下内核中是用.o作模块文件,我们以2.6的内核为准,以下同)。当应用程序需要时再加载到内核空间运行。所以我们所说的一个硬件的驱动程序,通常指的就是一个驱动模块。
设备文件:对于一个设备,它可以在/dev下面存在一个对应的逻辑设备节点,这个节点以文件的形式存在,但它不是普通意义上的文件,它是设备文件,更确切的说,它是设备节点。这个节点是通过mknod命令建立的,其中指定了主设备号和次设备号。主设备号表明了某一类设备,一般对应着确定的驱动程序;次设备号一般是区分不同属性,例如不同的使用方法,不同的位置,不同的操作。这个设备号是从/proc/devices文件中获得的,所以一般是先有驱动程序在内核中,才有设备节点在目录中。这个设备号(特指主设备号)的主要作用,就是声明设备所使用的驱动程序。驱动程序和设备号是一一对应的,当你打开一个设备文件时,操作系统就已经知道这个设备所对应的驱动程序。对于一个硬件,Linux是这样来进行驱动的:首先,我们必须提供一个.ko的驱动模块文件。我们要使用这个驱动程序,首先要加载它,我们可以用insmod
xxx.ko,这样驱动就会根据自己的类型(字符设备类型或块设备类型,例如鼠标就是字符设备而硬盘就是块设备)向系统注册,注册成功系统会反馈一个主设备号,这个主设备号就是系统对它的唯一标识。驱动就是根据此主设备号来创建一个一般放置在/dev目录下的设备文件。在我们要访问此硬件时,就可以对设备文件通过open、read、write、close等命令进行。而驱动就会接收到相应的read、write操作而根据自己的模块中的相应函数进行操作了。

USB驱动程序实践
了解了上述理论后,我们就可以动手写驱动程序,如果你基本功好,而且写过linux下的硬件驱动,USB的硬件驱动和pci_driver很类似,那么写USB的驱动就比较简单了,如果你只是大体了解了linux的硬件驱动,那也不要紧,因为在linux的内核源码中有一个框架程序可以拿来借用一下,这个框架程序在/usr/src/~(你的内核版本,以下同)/drivers/usb下,文件名为usb-skeleton.c。写一个USB的驱动程序最基本的要做四件事:驱动程序要支持的设备、注册USB驱动程序、探测和断开、提交和控制urb(USB请求块)(当然也可以不用urb来传输数据,下文我们会说到)。
驱动程序支持的设备:有一个结构体struct
usb_device_id,这个结构体提供了一列不同类型的该驱动程序支持的USB设备,对于一个只控制一个特定的USB设备的驱动程序来说,struct
usb_device_id表被定义为:
/* 驱动程序支持的设备列表 */
static struct usb_device_id
skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID)
},
{ } /* 终止入口 */
};
MODULE_DEVICE_TABLE (usb,
skel_table);
对于PC驱动程序,MODULE_DEVICE_TABLE是必需的,而且usb必需为该宏的第一个值,而USB_SKEL_VENDOR_ID和USB_SKEL_PRODUCT_ID就是这个特殊设备的制造商和产品的ID了,我们在程序中把定义的值改为我们这款USB的,如:
/*
定义制造商和产品的ID号 */
#define USB_SKEL_VENDOR_ID 0x1234
#define
USB_SKEL_PRODUCT_ID
0x2345
这两个值可以通过命令lsusb,当然你得先把USB设备先插到主机上了。或者查看厂商的USB设备的手册也能得到,在我机器上运行lsusb是这样的结果:
Bus
004 Device 001: ID 0000:0000
Bus 003 Device 002: ID 1234:2345 Abc Corp.

Bus 002 Device 001: ID 0000:0000
Bus 001 Device 001: ID
0000:0000
得到这两个值后把它定义到程序里就可以了。
注册USB驱动程序:所有的USB驱动程序都必须创建的结构体是struct
usb_driver。这个结构体必须由USB驱动程序来填写,包括许多回调函数和变量,它们向USB核心代码描述USB驱动程序。创建一个有效的struct
usb_driver结构体,只须要初始化五个字段就可以了,在框架程序中是这样的:
static struct usb_driver skel_driver
= {
.owner = THIS_MODULE,
.name = "skeleton",

.probe = skel_probe,
.disconnect = skel_disconnect,

.id_table = skel_table,
};

8. linux设备驱动程序开发应该怎么入手

不说说你当前基础吗?
1.熟练使用C语言和基本数据结构
2.熟悉Linux环境编程环境
3.良好的数字电路基础
4.了解操作系统原理,了解linux驱动的基本原理和实现方法
可按上面顺序学习,多动手实践

9. 嵌入式学习书籍有哪些

以下是华清远见·星创客嵌入式精英训练营提供的学习嵌入式必看的100本书:
001《大话数据结构》
002《鸟哥的 linux 私房菜》
003《疯狂 android 讲义》
004《第一行代码》
005《linux 内核设计与实现》
006《驱动设计开发》
007《linux 内核解密》
008《unix 环境高级编程》
009《linux 内核设计与实现》
010《essential C++》
011《嵌入式 linux》
012《linux 设备驱动》
013《c 语言深度解剖》
014《linux 下的 c编程》
015《C Primer Plus(第五版)》
016《ARM 体系结构与编程(第二版)》
017《lINUX 设备驱动开发详解(第三版)》
018《android 开发艺术探讨》
019《c++plus》
020《Unix 环境高级编程》
021《与大数据同行——学习和教育的未来》
022《用户体验的要素》
023《编程与艺术》
024《ARM 嵌入式体系结构与接口技术》
025《cortex-m0 接口编程》
026《C 语言程序设计:现代方法》
027《C++ Primer》
028《数据结构》(严蔚敏)
029《算法导论》
030《Linux 设备驱动开发》
031《代码大全》
032《深入理解计算机系统》
033《UNIX 环境高级编程》
034《计算机安全原理》
035《UNIX 网络编程》
036《HeadFirst 设计模式》
037《linux 驱动》(宋保华)
038《C++ primer4》
039《qt5 精彩实例》
040《ldd3》
041《C++高级编程》
042《C语言教程》
043《实战 linux 编程精髓》
044《ARM 教程》
045《JAVA 编程思想》
046《HTML+CSS 网页设计与布局从入门到精通》
047《C 语言深度解剖》
048《深度实践嵌入式 Linux 系统移植》
049《unix 高级编程》
050《c 嵌入式一站式教学》
051《编译原理》
052《深度实践嵌入式 Linux 系统移植》
053《UNIX 环境高级编程》
054《linux 网络编程》
055《C 语言程序设计》
056《unix 环境高级编程》
057《嵌入式 linuxc 语言程序设计基础教程》
058《Java 编程思想》
059《TCP/IP 详解》
060《linux 技术手册》
061《C 语言深度剖析》
062《Unix 高级环境编程》
063《C++primerplus》
064《QT》
065《C 程序设计》
066《C 和指针》
067《C++primer》
068《C 程序设计语言》
069《ProgrammingC#》
070《thinking in C++》
071《Linux Device driver》
072《Linux kernel development》
073《软件工程》
074《C 和指针》
075《Android 核心代码》
076《Android 技术内幕》
077《Android 底层移植》
078《Unix 编程手册(上下卷)》
079《Linux 驱动设计第三版》
080《ARM 实战开发》
081《unix 环境高级编程》
082《tcp/ip 编程详解》
083《Linux 网络编程》
084《Unix 编程艺术》
085《计算机程序的构造和解释》
086《C Primer plus》
087《LINUX 权威指南》
088《LINUX 设备驱动程序》
089《The C Programming Language》
090《ajax 高级程序设计》
091《angula js 权威教程》
092《ARM 体系结构》
093《Unix 环境高级编程》
094《Linux 设备驱动程序》
095《现代操作系统》
096《TCP/IP 协议详解》
097《嵌入式 C 语言设计模式》
098《Struts In Action》
099《c 程序设计语言(第二版)》
100《深入理解 Linux 内核(第三版)》

热点内容
云服务器选择什么系统 发布:2024-05-09 01:55:51 浏览:968
mel脚本编程全攻略 发布:2024-05-09 01:54:43 浏览:479
如何在机房安装ntp服务器 发布:2024-05-09 01:13:57 浏览:206
ideajavaidea 发布:2024-05-09 01:02:14 浏览:965
oas存储 发布:2024-05-09 00:57:49 浏览:801
android点击弹出菜单 发布:2024-05-09 00:56:52 浏览:99
大家对云服务器认知度 发布:2024-05-09 00:46:00 浏览:659
思科视频会议如何配置 发布:2024-05-09 00:45:59 浏览:669
centos安装ftp服务器配置 发布:2024-05-09 00:45:06 浏览:81
幕布电脑版服务器连接失败怎么整 发布:2024-05-09 00:38:21 浏览:723