导航:首页 > 操作系统 > linuxmmap驱动

linuxmmap驱动

发布时间:2022-08-24 03:59:54

linux驱动mmap

使用共享内存前,调用msync()试试

㈡ 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打印调试信息,倒也还凑合。

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

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

㈢ Linux关于地址空间和MMAP映射有何特点

Linux采用
虚拟
内存技术,系统中的所有进程之间以虚拟方式共享内存。对每个进程来说,它们好像都可以访问整个系统的所有物理内存。更重要的是,即使单独一个进程,它拥有的地址空间也可以远远大于系统物理内存。
进程地址空间由每个进程中的线性地址区组成,每个进程都有一个32位或64位的平坦(flat)空间,空间的具体大小取决于体系结构。“平坦”指地址空间范围是一个独立的连续区间。通常情况下,每个进程都有唯一的这种平坦空间,而且每个进程的地址空间之间彼此互不相干。两个不同的进程可以在它们各自地址空间的相同地址内存存放不同的数据。但是进程之间也可以选择共享地址空间,我们称这样的进程为线程。
在地址空间中,我们更为关心的是进程有权访问的虚拟内存地址区间,比如08048000~0804c000。这些可被访问的合法地址区间被成为内存区域(memory area),通过内核,进程可以给自己的地址空间动态地添加或减少内存区域。
进程只能访问有效范围内的内存地址。每个内存区域也具有相应进程必须遵循的特定访问属性,如只读、只写、可执行等属性。如果一个进程访问了不在有效范围中的地址,或以不正确的方式访问有效地址,那么内核就会终止该进程,并返回“段错误”信息。
?
内存区域可以包含各种内存对象,如下:
?
可执行文件代码的内存映射,成为代码段(text section)。
?
可执行文件的已初始化全局变量的内存映射,成为数据段(data section)。
?
包含未初始化全局变量的零页(也就是bss段)的内存映射。零页是指页面中的数据全部为0。
?
用于进程用户空间栈的零页的内存映射。
?
每一个诸如C库或动态链接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间。
?
任何内存映射文件。
?
任何共享内存段。
?
任何匿名的内存映射,比如由malloc()分配的内存。
进程地址空间的任何有效地址都只能位于唯一的区域,这些内存区域不能相互覆盖。可以看到,在执行的进程中,每个不同的内存片断都对应一个独立的内存区域:栈、对象代码、全局变量、被映射的文件等等。
内核使用内存描述符表示进程的地址空间。内存描述符由mm_struct结构体表示,定义在文件中,该结构包含了和进程地址空间有关的全部信息。
VMA
内存区域由vm_area_struct结构体描述,定义在文件中,内存区域在内核中也经常被称作虚拟内存区域或者VMA。
VMA标志是一种位标志,它定义在vm_area_struct结构中(该结构中的vm_flags子域)。和物理页的访问权限不同,VMA标志反映了内核处理页面索需要遵守的行为准则,而不是硬件要求。VM_IO标志内存区域中包含对设备I/O空间的映射。该标志通常在设备驱动程序执行 mmap()函数进行I/O空间映射时才被设置,同时该标志也表示该内存区域不能被包含在任何进程的存放转存(core mp)中。VM_RESERVED标志内存区域不能被换出,它也是在设备驱动程序进行映射时被设置。
vm_area_struct结构体中的vm_ops域指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。
mmap()和do_mmap():创建地址区间
内核使用do_mmap()函数创建一个新的线性地址区间。但是说给函数创建一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况,do_mmap()函数都会将一个地址区间加入到进程的地址空间中——无论是扩展已经存在的内存区域还是创建一个新的区域。
do_mmap()函数声明在文件中,原型如下:
unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
在用户空间可以通过mmap()函数调用获取内核函数do_mmap()的功能。mmap()系统调用原型如下:
void *mmap2(void *start, size_t length,
int prot, int flags,
int fd, off_t pgoff)
do_munmap()函数从特定的进程地址空间中删除指定地址区间,该函数在文件中声明:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
系统调用munmap()给用户空间程序提供了一种从自身地址空间中删除指定地址区间的方法,它和系统调用mmap()的作用相反:
int munmap(void *start, size_t length)
mmap设备操作
对于驱动程序来说,内存映射可以提供给用户程序直接访问设备内存的能力。映射一个设备,意味着使用户空间的一段地址关联到设备内存上。无论何时,只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。
并不是所有的设备都能进行mmap抽象。例如,串口设备和其他面向流的设备就无法实现这种抽象。mmap的另一个限制是映射都是以 PAGE_SIZE为单位的。内核只能在页表一级处理虚拟地址;因此,被映射的区域必须是PAGE_SIZE的整数倍,而且必须位于起始于 PAGE_SIZE整数倍地址的物理内存内。如果区域的大小不是页大小的整数倍,内核就通过生成一个稍微大一些的区域来容纳它。
mmap方法是file_operations结构中的一员,并且在执行mmap系统调用时就会调用该方法。在调用实际方法之前,内核会完成很多工作,而且该方法的原型与系统调用的原型由很大区别。关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html
文件操作声明如下:
int (*mmap) (struct file * filp, struct vm_area_struct *vma);
其中vma参数包含了用于访问设备的虚拟地址区间的信息。大部分工作已经由内核完成了,要实现mmap,驱动程序只要为这一地址范围构造合适的页表即可,如果需要的话,就用一个新的操作集替换vma->vm_ops。
有两种建立页表的方法:使用remap_page_range函数可一次建立所有的页表,或者通过nopage VMA方法每次建立一个页表。
构造用于映射一段物理地址的新页表的工作是由remap_page_range完

㈣ linux设备驱动物理内存映射

int video_qsb_mmap(struct file *file,struct vm_area_struct *vma)
{
int ret;
u32 size = vma->vm_end - vma->vm_start;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_flags |= VM_RESERVED;
if(frm_num<qsb_dev.mmap_num)
{
ret = remap_pfn_range(vma,vma->vm_start,align_addr>>PAGE_SHIFT,qsb_dev.stride*1944,vma->vm_page_prot); align_addr=PAGE_ALIGN(align_addr+2592*1944);
if(ret != 0)
{ return -EAGAIN; }
frm_num++; }
return 0;
}

这是我自己的函数,我是在UBOOT里规定系统只能用前192M,其它的由应用层调用MMAP函数实现映射,贴过来格式乱了,你参考参考,是可以用的。大概就是申请1张图片的大小,并进行页对齐。不懂的问,你查查这几个函数的作用参数照着写应该就可以实现你得目的了。

㈤ Linux将设备地址映射到用户空间内存映射与VMA

一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap ()函数,这个函数可使得用户空间能直接访问设备的物理地址。实际上,mmap ()实现了这样的一个映射过程:它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。
这种能力对于显示适配器一类的设备非常有意义,如果用户空间可直接通过内存映射访问显存的话,屏幕帧的各点像素将不再需要一个从用户空间到内核空间的复制的过程。
mmap ()必须以PAGE_SIZE为单位进行映射,实际上,内存只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行页对齐,强行以PAGE_SIZE的倍数大小进行映射。
从file_operations文件操作结构体可以看出,驱动中mmap ()函数的原型如下:
int ( *mmap)(struct file *, struct vm_area_struct* ) ;
驱动中的mmap () 函数将在用户进行mmap ()系统调用时最终被调用,mmap ()系统调用的原型与file_operations中mmap ()的原型区别很大,如下所示:
caddr_t mmap (caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);
参数fd为文件描述符,一般由open ()返回,fd也可以指定为-1,此时需指定flags参数中的MAP_ANON,表明进行的是匿名映射。
len是映射到调用用户空间的字节数,它从被映射文件开头offset个字节开始算起,offset参数一般设为0,表示从文件头开始映射。
prot参数指定访问权限,可取如下几个值的“或”:PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)和PROT_NONE(不可访问)。
参数addr指定文件应被映射到用户空间的起始地址,一般被指定为NULL,这样,选择起始地址的任务将由内核完成,而函数的返回值就是映射到用户空间的地址。其类型caddr_t实际上就是void*。
当用户调用mmap ())的时候,内核会进行如下处理。
1)在进程的虚拟空间查找一块VMA。
2)将这块VMA进行映射。
3)如果设备驱动程序或者文件系统的file_operations定义了mmap ()操作,则调用它。
4)将这个VMA插入进程的VMA链表中。
file_operations中mmap ()函数的第一个参数就是步骤1)找到的VMA。
由mmap ()系统调用映射的内存可由munmap ()解除映射,这个函数的原型如下:
int munmap(caddr_t addr, size_t len ) ;
驱动程序中mmap ()的实现机制是建立页表,并填充VMA结构体中vm_operations_struct指针。

㈥ 如何在Linux下开发摄像头驱动

在linux下所有设备都是文件。所以对摄像头的操作其实就是对文件的操作。USB摄像头的设备文件就是在/dev目录下的video0(假如只有一个摄像头)。在linux下操作摄像头就是使用v4l2对摄像头进行视频的操作,操作步骤如下
1. 打开设备文件。
int fd=open(”/dev/video0″,O_RDWR);
2. 取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability
v4l2_std_id std;
do {
ret= ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC:
//……
case V4L2_STD_PAL:
//……
}
3. 选择视频输入,一个视频设备可以有多个视频输入。VIDIOC_S_INPUT,struct v4l2_input(可不要)
4. 设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。
VIDIOC_S_STD,VIDIOC_S_FMT,struct v4l2_std_id,struct v4l2_format
struct v4l2_format fmt;
memset ( &fmt, 0, sizeof(fmt) );
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 320;
fmt.fmt.pix.height = 240;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0)
{
printf("set format failed\n");
//return 0;
}
5. 向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof (req));
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd,VIDIOC_REQBUFS,&req) == -1)
{
perror("VIDIOC_REQBUFS error \n");
//return -1;
}
6.申请物理内存
将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer
VideoBuffer* buffers = calloc( req.count, sizeof(VideoBuffer) );
printf("sizeof(VideoBuffer) is %d\n",sizeof(VideoBuffer));
struct v4l2_buffer buf;
for (numBufs = 0; numBufs < req.count; numBufs++)
{
memset( &buf, 0, sizeof(buf) );
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0)
{
printf("VIDIOC_QUERYBUF error\n");
//return -1;
}
printf("buf len is %d\n",sizeof(buf));
//内存映射
buffers[numBufs].length = buf.length;
buffers[numBufs].offset = (size_t) buf.m.offset;
buffers[numBufs].start = mmap (NULL, buf.length,PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
printf("buffers.length = %d,buffers.offset = %d ,buffers.start[0] = %d\n",buffers[numBufs].length,buffers[numBufs].offset,buffers[numBufs].start[0]);
printf("buf2 len is %d\n",sizeof(buffers[numBufs].start));
if (buffers[numBufs].start == MAP_FAILED)
{
perror("buffers error\n");
//return -1;
}
if (ioctl (fd, VIDIOC_QBUF, &buf) < 0)
{
printf("VIDIOC_QBUF error\n");
//return -1;
}
}
7. 开始视频的采集。
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl (fd, VIDIOC_STREAMON, &type) < 0)
{
printf("VIDIOC_STREAMON error\n");
// return -1;
}
8. 出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF, 将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0)
{
perror("VIDIOC_DQBUF failed.\n");
//return -1;
}
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
unsigned char *ptcur = buffers[numBufs].start;
DEBUG("buf.bytesused = %d \n",buf.bytesused);
int i1;
for(i1=0; i1<buf.bytesused; i1++)
{
if((buffers[numBufs].start[i1] == 0x000000FF) && (buffers[numBufs].start[i1+1] == 0x000000C4))
{
DEBUG("huffman table finded! \nbuf.bytesused = %d\nFFC4 = %d \n",buf.bytesused,i1);
break;
}
}
if(i1 == buf.bytesused)printf("huffman table don't exist! \n");
int i;
for(i=0; i<buf.bytesused; i++)
{
if((buffers[numBufs].start[i] == 0x000000FF) && (buffers[numBufs].start[i+1] == 0x000000D8)) break;
ptcur++;
}
DEBUG("i=%d,FF=%02x,D8=%02x\n",i,buffers[numBufs].start[i],buffers[numBufs].start[i+1]);
int imagesize =buf.bytesused - i;
DEBUG("buf.bytesused = %d \n",buf.bytesused);
DEBUG ("imagesize = %d \n",imagesize);
9. 停止视频的采集。VIDIOC_STREAMOFF
10. 关闭视频设备。close(fd);

㈦ 解释一下linux驱动程序结构框架及工作原理

一、Linux device driver 的概念

系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:

1、对设备初始化和释放;

2、把数据从内核传送到硬件和从硬件读取数据;

3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;

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

在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。

已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。

最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。

二、实例剖析

我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。

由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 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 <linux/types.h> 基本的类型定义

#include <linux/fs.h> 文件系统使用相关的头文件

#include <linux/mm.h>

#include <linux/errno.h>

#include <asm/segment.h>

unsigned int test_major = 0;

static int read_test(struct inode *inode,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。为了验证BUF是否可以用。

static int write_test(struct inode *inode,struct file *file,const char *buf,int count)

{

return count;

}

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

{

MOD_INC_USE_COUNT; 模块计数加以,表示当前内核有个设备加载内核当中去

return 0;

}

static void release_test(struct inode *inode,struct file *file )

{

MOD_DEC_USE_COUNT;

}

这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。

struct file_operations test_fops = {?

read_test,

write_test,

open_test,

release_test,

};

设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进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\n");

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 –c表示输出制定名,自动生成.o文件

得到文件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

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

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

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

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

main()

{

int testdev;

int i;

char buf[10];

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

if ( testdev == -1 )

{

printf("Cann't open file \n");

exit(0);

}

read(testdev,buf,10);

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

printf("%d\n",buf[i]);

close(testdev);

}

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

以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。上述给出了一个简单的字符设备驱动编写的框架和原理,更为复杂的编写需要去认真研究LINUX内核的运行机制和具体的设备运行的机制等等。希望大家好好掌握LINUX设备驱动程序编写的方法。

㈧ 如何编写Linux 驱动程序

如何编写Linux设备驱动程序
回想学习Linux操作系统已经有近一年的时间了,前前后后,零零碎碎的一路学习过来,也该试着写的东西了。也算是给自己能留下一点记忆和回忆吧!由于完全是自学的,以下内容若有不当之处,还请大家多指教。
Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便。
以下的一些文字主要来源于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操作系统下的设备驱动程序

二、实例剖析
我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。
#define __NO_VERSION__
#include <linux/moles.h>
#include <linux/version.h>
char kernel_version [] = UTS_RELEASE;
这一段定义了一些版本信息,虽然用处不是很大,但也必不可少。Johnsonm说所有的驱动程序的开头都要包含<linux/config.h>,一般来讲最好使用。
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 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 <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include<linux/config.h>
#include <linux/errno.h>
#include <asm/segment.h>
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提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考Robert着的《Linux内核设计与实现》(第二版)。然而,在向用户空间拷贝数据之前,必须验证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\n");
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
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d\n",buf[i]);
close(testdev);
}

编译运行,看看是不是打印出全1 ?
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。
如何编写Linux操作系统下的设备驱动程序
三、设备驱动程序中的一些具体问题
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:I/O端口的基地址,
参数2:I/O端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的I/O口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当于访问一段内存。经常性的,我们要获得一块内存的物理地址。

2。内存操作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages。 请注意,kmalloc等函数返回的是物理地址!
注意,kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。
内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块程序需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。

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。一些常见的问题。
对硬件操作,有时时序很重要(关于时序的具体问题就要参考具体的设备芯片手册啦!比如网卡芯片RTL8139)。但是如果用C语言写一些低级的硬件操作的话,gcc往往会对你的程序进行优化,这样时序会发生错误。如果用汇编写呢,gcc同样会对汇编代码进行优化,除非用volatile关键字修饰。最保险的办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现出来。
写在后面:学习Linux确实不是一件容易的事情,因为要付出很多精力,也必须具备很好的C语言基础;但是,学习Linux也是一件非常有趣的事情,它里面包含了许多高手的智慧和“幽默”,这些都需要自己亲自动手才能体会到,O(∩_∩)O~哈哈!

阅读全文

与linuxmmap驱动相关的资料

热点内容
海南压缩机在哪里 浏览:487
电脑文件夹清晰的文件结构 浏览:837
如何把苹果手机的app转到安卓 浏览:303
java同步并发 浏览:249
fw压缩图片 浏览:256
淘宝申请源码靠谱吗 浏览:872
androidupdater 浏览:635
c2d游戏源码大全可复制版 浏览:771
电脑怎样重置网关命令 浏览:411
winftplinux 浏览:335
推特app界面如何设置成中文 浏览:452
太空工程师转子编程属性 浏览:32
windowscmd关机命令 浏览:342
云桌面只要服务器装一套软件 浏览:247
电脑右键按到什么导致文件夹全屏 浏览:454
我的世界如何制造服务器主城 浏览:365
linuxssh连不上 浏览:297
永宏plc用什么编程电缆 浏览:371
win激活命令行 浏览:886
新手学电脑编程语言 浏览:893