linux驱动程序进入内核
ARM-uClinux下编写加载驱动程序详细过程本文主要介绍在uClinux下,通过加载模块的方式调试IO控制蜂鸣器的驱动程序。
实验过程与上篇文章所讲的过程基本相似,更多注重细节及注意事项。
本文适合学习ARM—Linux的初学者。
//==================================================================硬件平台:MagicARM2200教学试验开发平台(LPC2290)Linux version 2.4.24,gcc version 2.95.3电路连接:P0.7——蜂鸣器,低电平发声。
实验条件:uClinux内核已经下载到开发板上,能够正常运行;与宿主机相连的网络、串口连接正常。
//==================================================================编写蜂鸣器的驱动程序相对来说容易实现,不需要处理中断等繁琐的过程,本文以蜂鸣器的驱动程序为例,详细说明模块化驱动程序设计的主要过程和注意事项。
一、编写驱动程序驱动程序的编写与上文所说的编写过程基本相同,这里再详细说明一下。
//==========================================//蜂鸣器驱动程序:beep.c文件//-------------------------------------------------------------------#include <linux/module.h> /*模块相关*/#include <linux/kernel.h> /*内核相关*/#include <linux/types.h> /*linux定义类型*/#include <linux/fs.h> /*文件系统 file_opertions 结构体定义*/#include <linux/errno.h> /*出错信息*//*PINSEL0 注意:低2位是UART0复用口,不要改动*/#define PINSEL0 (*((volatile unsigned*) 0xE002C000))/*P0口控制寄存器*/#define IO0PIN (*((volatile unsigned*) 0xE0028000))#define IO0SET (*((volatile unsigned*) 0xE0028004))#define IO0DIR (*((volatile unsigned*) 0xE0028008))#define IO0CLR (*((volatile unsigned*) 0xE002800C))#define MAJOR_NUMBER 254 /*自定义的主设备号*/#define BEEP_CMD 0 /*自定义的控制命令*//*函数声明*/static int beep_open(struct inode *inode, struct file *file);static int beep_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);static int beep_release(struct inode *inode, struct file *file);static int beep_init(void);static void beep_cleanup(void);/********************************************************/volatile static int beep_major = MAJOR_NUMBER; /*全局变量:主设备号自定义为254*//********************************************************//*注册函数:用到file_operations结构体。
将蜂鸣器结构体自命名为 beep_test ,在注册模块时要用到*/static struct file_operations beep_test ={owner : THIS_MODULE,ioctl : beep_ioctl,open : beep_open,release : beep_release,}; /*注意:此处的分号(;)不要丢掉*//*********************************************************/#define BEEPCON 0x00000080static void beep_port_init(void) //蜂鸣器端口初始化:设置P0.7口为输出,初始值为高(蜂鸣器不发声){IO0DIR = BEEPCON;IO0SET = BEEPCON;}static void beep(int beep_status) //蜂鸣器操作:根据参数(beep_status)状态判断是否发声{if(beep_status == 0)IO0CLR = BEEPCON;elseIO0SET = BEEPCON;}static int beep_open(struct inode *inode, struct file *file) //beep_test结构体中的open()函数实体,以下同{MOD_INC_USE_COUNT; //注册模块数加1beep_port_init();return 0;}static int beep_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){if(cmd == 0){printk("beep on!\n");beep(0);}else{printk("beep off!\n");beep(1);}return 0;}static int beep_release(struct inode *inode, struct file *file){MOD_DEC_USE_COUNT; //模块数减1return 0;}static int beep_init(void) //模块加载、初始化函数:将模块加载到内核运行{int result;result = register_chrdev(beep_major, "named_beep", &beep_test);if(result < 0){printk(KERN_INFO"beep: can't get major number\n");return result;}if(beep_major == 0) beep_major = result;printk(KERN_INFO"beep: init OK!\n");/*注意:驱动程序运行在内核空间,从内核打印信息要用printk()函数而不是printf()函数,而且要配有优先级*/return 0;}static void beep_cleanup(void) //模块卸载函数:将模块从内核卸载出去{unregister_chrdev(beep_major, "named_beep");}/************************************************************//*以下部分是驱动程序的关键,后面做详细说明*///module_init(beep_init);//module_exit(beep_cleanup);int init_module(void) //加载模块{return beep_init();}void cleanup_module(void) //卸载模块{beep_cleanup();}//-------------------------------------------------------------------//驱动程序文件结束//==========================================以上是整个驱动程序文件的全部内容,将文件保存,这里将其命名为beep.c。
整个驱动程序很简单,只填写了几个操作函数 beep_open()、beep_release()和beep_ioctl()。
其实控制蜂鸣器用beep_ioctl()一个函数即可,其它函数基本都是空操作。
在驱动文件最后的两个函数对驱动程序来数是及其重要的。
应用程序与内核的区别就是应用程序从头到尾完成一个任务,而内核则为以后处理某些请求而注册自己,完成这个任务后,他的“主”函数就立即终止。
换句话说,init_module()函数(名称不能更改)是模块入口点,如同应用程序的main()函数一样,换句话说,模块入口点init_module()函数的任务就是为以后调用模块的函数做准备;cleanup_module()函数(名称不能更改)是模块的第二个入口点,此函数仅当模块被卸载前才被调用。
它的功能是去掉init_module()函数所作的事情。
这两个函数由<linus/modele.h>头文件声明,有关模块实现的源代码可以参见../kernel/module.c。
init_module()函数在模块被加载时执行,模块的初始化就是通过调用init_module()函数完成的。
它注册驱动设备,需调用register_chrdev()函数实现。
register_chrdev有3个参数:(1):希望获得的设备主号,即beep_major全局变量,如果是0,系统将选择一个没有被占用的设备号返回;(2):设备文件名,自定义设备文件名,这里用named_beep,它返回这个驱动程序所使用的主设备号;(3):用来登记驱动程序实际执行操作的函数指针,即beep_test结构体。
如果登记成功,register_chrdev返回设备的主设备号;否则返回一个负值。
模块是内核的一部分,但并未被编辑到内核中,他们被分别编译和连接成目标文件。
用命令insmod 插入一个模块到内核中,用命令rmmod卸载一个模块。
这两个命令分别调用init_module()函数和cleanup_module()函数。
Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解
Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解目前,Linux软件工程师大致可分为两个层次:01Linux应用软件工程师(ApplicaTIon Software Engineer):主要利用C库函数和Linux API 进行应用软件的编写;从事这方面的开发工作,主要需要学习:符合linux posix标准的API函数及系统调用,linux 的多任务编程技巧:多进程、多线程、进程间通信、多任务之间的同步互斥等,嵌入式数据库的学习,UI编程:QT、miniGUI等。
02Linux固件工程师(Firmware Engineer):主要进行Bootloader、Linux的移植及Linux设备驱动程序的设计工作。
一般而言,固件工程师的要求要高于应用软件工程师的层次,而其中的Linux设备驱动编程又是Linux程序设计中比较复杂的部分,究其原因,主要包括如下几个方面:1 )设备驱动属于Linux内核的部分,编写Linux设备驱动需要有一定的Linux操作系统内核基础;需要了解部分linux内核的工作机制与系统组成2)编写Linux设备驱动需要对硬件的原理有相当的了解,大多数情况下我们是针对一个特定的嵌入式硬件平台编写驱动的,例如:针对特定的主机平台:可能是三星的2410、2440,也可能是atmel的,或者飞思卡尔的等等3 )Linux设备驱动中广泛涉及到多进程并发的同步、互斥等控制,容易出现bug;因为linux本身是一个多任务的工作环境,不可避免的会出现在同一时刻对同一设备发生并发操作4 )由于属于内核的一部分,Linux设备驱动的调试也相当复杂。
linux设备驱动没有一个很好的IDE环境进行单步、变量查看等调试辅助工具;linux驱动跟linux内核工作在同一层次,一旦发生问题,很容易造成内核的整体崩溃。
在任何一个计算机系统中,大至服务器、PC机、小至手机、mp3/mp4播放器,无论是复杂的大型服务器系统还是一个简单的流水灯单片机系统,都离不开驱动程序的身影,没有硬件的软件是空中楼阁,没有软件的硬件只是一堆废铁,硬件是底层的基础,是所有软件。
linux下静态动态加载驱动程序的方法
linux下静态/动态加载驱动程序的方法linuxfrom:/bbs/viewthread.p hp?tid=91244linux下静态/动态加载驱动程序的方法说明:这是我最近给单位写的一篇文档,没有什么复杂的东东,对刚接触linuxdriver的朋友或许有点帮助。
文档本来是针对我们自己的产品的,有些地方(路径、mknod、动态分配主设备号等)本来应该改改,因为懒惰也没去改。
在LINUX下加载驱动程序可以采用动态和静态两种方式。
静态加载就是把驱动程序直接编译到内核里,系统启动后可以直接调用。
静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译下载内核,效率较低。
动态加载利用了LINUX的modul e特性,可以在系统启动后用insmod命令把驱动程序(.o文件)添加上去,在不需要的时候用rmmod 命令来卸载。
在台式机上一般采用动态加载的方式。
在嵌入式产品里可以先用动态加载的方式来调试,调试完毕后再编译到内核里。
下面以我们的nHD板卡为例讲述一下加载驱动程序的方法。
假设我们需要添加一个名为mydrv的字符型设备驱动,主设备号为254,次设备号为0(只有一个从设备)。
静态加载的步骤如下:1、编写自己的驱动程序源文件mydrv.c,并放在firmware\uClinux-Samsung-2500\linux-2.4.x\drivers\char下面。
一个典型的字符型驱动的最后一般包括如下内容:static int mydrv_init(void){int ret;ret=register_chrdev(mydrv_major,"mydrv",&my_fops);if(ret==0)printk("register_chrdev succeed!\n");else printk("register_chrdev fail!\n");return0;}static__exit void mydrv_cleanup(void){unregister_chrdev(mydrv_major,"mydrv");printk("register_chrdev succeed!\n");return;}module_init(mydrv_init);module_exit(mydrv_cleanup);函数mydrv_init的任务是注册设备,mydrv_cleanup的任务是取消注册。
嵌入式linux系统的启动流程
嵌入式linux系统的启动流程
嵌入式Linux系统的启动流程一般包括以下几个步骤:
1.硬件初始化:首先会对硬件进行初始化,例如设置时钟、中
断控制等。
这一步骤通常是由硬件自身进行初始化,也受到系统的BIOS或Bootloader的控制。
2.Bootloader引导:接下来,系统会从存储介质(如闪存、SD
卡等)的Bootloader区域读取引导程序。
Bootloader是一段程序,可以从存储介质中加载内核镜像和根文件系统,它负责进行硬件初始化、进行引导选项的选择,以及加载内核到内存中。
3.Linux内核加载:Bootloader会将内核镜像从存储介质中加载到系统内存中。
内核镜像是包含操作系统核心的一个二进制文件,它由开发者编译并与设备硬件特定的驱动程序进行连接。
4.内核初始化:一旦内核被加载到内存中,系统会进入内核初
始化阶段。
在这个阶段,内核会初始化设备驱动程序、文件系统、网络协议栈等系统核心。
5.启动用户空间:在内核初始化完毕后,系统将启动第一个用
户空间进程(init进程)。
init进程会读取并解析配置文件(如
/etc/inittab)来决定如何启动其他系统服务和应用程序。
6.启动其他系统服务和应用程序:在用户空间启动后,init进
程会根据配置文件启动其他系统服务和应用程序。
这些服务和应用程序通常运行在用户空间,提供各种功能和服务。
以上是嵌入式Linux系统的基本启动流程,不同的嵌入式系统可能会有一些差异。
同时,一些特定的系统也可以添加其他的启动流程步骤,如初始化设备树、加载设备固件文件等。
Linux设备驱动程序原理及框架-内核模块入门篇
Linux设备驱动程序原理及框架-内核模块入门篇内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块内核模块介绍Linux采用的是整体式的内核结构,这种结构采用的是整体式的内核结构,采用的是整体式的内核结构的内核一般不能动态的增加新的功能。
为此,的内核一般不能动态的增加新的功能。
为此,Linux提供了一种全新的机制,叫(可安装) 提供了一种全新的机制,可安装) 提供了一种全新的机制模块” )。
利用这个机制“模块”(module)。
利用这个机制,可以)。
利用这个机制,根据需要,根据需要,在不必对内核重新编译链接的条件将可安装模块动态的插入运行中的内核,下,将可安装模块动态的插入运行中的内核,成为内核的一个有机组成部分;成为内核的一个有机组成部分;或者从内核移走已经安装的模块。
正是这种机制,走已经安装的模块。
正是这种机制,使得内核的内存映像保持最小,的内存映像保持最小,但却具有很大的灵活性和可扩充性。
和可扩充性。
内核模块内核模块介绍可安装模块是可以在系统运行时动态地安装和卸载的内核软件。
严格来说,卸载的内核软件。
严格来说,这种软件的作用并不限于设备驱动,并不限于设备驱动,例如有些文件系统就是以可安装模块的形式实现的。
但是,另一方面,可安装模块的形式实现的。
但是,另一方面,它主要用来实现设备驱动程序或者与设备驱动密切相关的部分(如文件系统等)。
密切相关的部分(如文件系统等)。
课程内容内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块应用层加载模块操作过程内核引导的过程中,会识别出所有已经安装的硬件设备,内核引导的过程中,会识别出所有已经安装的硬件设备,并且创建好该系统中的硬件设备的列表树:文件系统。
且创建好该系统中的硬件设备的列表树:/sys 文件系统。
(udev 服务就是通过读取该文件系统内容来创建必要的设备文件的。
)。
Linux数码管驱动详细过程
嵌入式Linux系统系列培训基于JXARM9-2410-1 实验嵌入式Linux中驱动程序的编写(静态加载方式,模块(动态)加载方式)与应用程序开发。
分5个步骤:①数码管驱动程序seg和应用程序segtest编译;②修改驱动程序seg虚拟地址空间---- smdk.c;③(静态或动态)加载驱动程序seg到linux内核(zlmage);④修改文件系统Ramdisk.gz,将应用程序segtest加入其中;⑤下载linux内核和文件系统,并自动运行应用程序运行。
一、在Linux下编译驱动程序,以学习机上的数码管显示驱动Seg应用程序为例。
数码管显示驱动和应用程序包括seg.h, seg.c, segtest.c segc四个文件,其中seg.h, seg.c是驱动程序,segtest.c是调用驱动的应用程序,segc是编译文件。
DEVICE_NAME: char_deV' (seg.c 定义)MAJOR_NUM: 96 (seg.h 定义)DEVICE_FILE_NAME: seg' (seg.h 定义)1. 将WinXP下的seg目录下的四个文件复制到VMware中的linux中的/home/cvtech/jx2410/examples/seg 目录下。
采用两种方法将winxp中的文件夹映射到VMware中的linux系统中。
方法①:(共享目录)在VMware中,选择;VM > Settings > Option, 选shared folders,eg:e:\segAdd 共享文件夹在VMware 下的linux 中,显示的文件夹为:/mnt/hgfs/seg方法② : (TFTP)通过tftp将winxp中的seg文件夹中的文件复制到VMware中的linux中,具体步骤:a)改winxp 主机IP 为192.168.1.160,子网掩码为:255.255.255.0,其它不填b)在VM > Linux 中,输入:$ifconfig eth0 192.168.1.180/ (激活IP)(Linux login:root, password:123456)c)在Winxp 下启动tftp,设置e:\seg为目录,serverinterface:192.168.1.160d)在VM > linux 中,执行$cd /home/cvtech/jx2401/examples/ $mkdir seg/$cd seg/$tftp 192.168.1.160/Tftp > get * . /(get seg.c按照文件名copy,copy 完,q 退出)$chmod 777 */ (设置刚复制的文件属性为可读写)以上步骤,则将winxp 中的下的文件,复制到VMware->linux 下的/home/cvtech/jx2401/examples/seg中。
Linux Kernel学习笔记——启动
Linux Kernel学习笔记——启动启动当PC启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFFF0处的代码,也就是ROM-BIOS起始位置的代码。
BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。
最后BIOS将启动盘的第一个扇区装入到0x7C00,并开始执行此处的代码.这就是对内核初始化过程的一个最简单的描述。
最初,Linux核心的最开始部分是用8086汇编语言编写的。
当开始运行时,核心将自己装入到绝对地址0x90000,再将其后的2k字节装入到地址0x90200处,最后将核心的其余部分装入到0x10000。
当系统装入时,会显示Loading...信息。
装入完成后,控制转向另一个实模式下的汇编语言代码boot/Setup.S。
Setup部分首先设置一些系统的硬件设备,然后将核心从0x10000处移至0x1000处。
这时系统转入保护模式,开始执行位于0x1000处的代码。
接下来是内核的解压缩。
0x1000处的代码来自于文件Boot/head.S,它用来初始化寄存器和调用decompress_kernel()程序。
decompress_kernel( )程序由Boot/inflate.c, Boot/unzip.c和Boot/misc.c组成。
解压缩后的数据被装入到了0x 处,这也是Linux不能在内存小于2M的环境下运行的主要原因。
解压后的代码在0x处开始执行,紧接着所有的32位的设置都将完成: IDT、GDT 和LDT将被装入,处理器初始化完毕,设置好内存页面,最终调用start_kernel过程。
这大概是整个内核中最为复杂的部分。
[系统开始运行]Linux kernel最早的C代码从汇编标记startup_32开始执行|startup_32:|start_kernel|lock_kernel|trap_init|init_IRQ|sched_init|softirq_init|time_init|console_init|#ifdef CONFIG_MODULES |init_modules|#endif|kmem_cache_init|sti|calibrate_delay|mem_init|kmem_cache_sizes_init |pgtable_cache_init|fork_init|proc_caches_init|vfs_caches_init|buffer_init|page_cache_init|signals_init|#ifdef CONFIG_PROC_FS|proc_root_init|#endif|#if defined(CONFIG_SYSVIPC)|ipc_init|#endif|check_bugs|smp_init|rest_init|kernel_thread|unlock_kernel|cpu_idle·startup_32 [arch/i386/kernel/head.S]·start_kernel [init/main.c]·lock_kernel [include/asm/smplock.h]·trap_init [arch/i386/kernel/traps.c]·init_IRQ [arch/i386/kernel/i8259.c]·sched_init [kernel/sched.c]·softirq_init [kernel/softirq.c]·time_init [arch/i386/kernel/time.c]·console_init [drivers/char/tty_io.c]·init_modules [kernel/module.c]·kmem_cache_init [mm/slab.c]·sti [include/asm/system.h]·calibrate_delay [init/main.c]·mem_init [arch/i386/mm/init.c]·kmem_cache_sizes_init [mm/slab.c]·pgtable_cache_init [arch/i386/mm/init.c]·fork_init [kernel/fork.c]·proc_caches_init·vfs_caches_init [fs/dcache.c]·buffer_init [fs/buffer.c]·page_cache_init [mm/filemap.c]·signals_init [kernel/signal.c]·proc_root_init [fs/proc/root.c]·ipc_init [ipc/util.c]·check_bugs [include/asm/bugs.h]·smp_init [init/main.c]·rest_init·kernel_thread [arch/i386/kernel/process.c]·unlock_kernel [include/asm/smplock.h]·cpu_idle [arch/i386/kernel/process.c]start_kernel( )程序用于初始化系统内核的各个部分,包括:*设置内存边界,调用paging_init( )初始化内存页面。
LINUX内核模块编译步骤
LINUX内核模块编译步骤编译Linux内核模块主要包括以下步骤:1.获取源代码2.配置内核进入源代码目录并运行make menuconfig命令来配置内核。
该命令会打开一个文本菜单,其中包含许多内核选项。
在这里,你可以配置内核以适应特定的硬件要求和预期的功能。
你可以选择启用或禁用各种功能、设备驱动程序和文件系统等。
配置完成后,保存并退出。
3. 编译内核(make)运行make命令开始编译内核。
这将根据你在上一步中进行的配置生成相应的Makefile,然后开始编译内核。
编译的过程可能需要一些时间,请耐心等待。
4.安装模块编译完成后,运行make modules_install命令将编译好的模块安装到系统中。
这些模块被安装在/lib/modules/<kernel-version>/目录下。
5.安装内核运行make install命令来安装编译好的内核。
该命令会将内核映像文件(通常位于/arch/<architecture>/boot/目录下)复制到/boot目录,并更新系统引导加载程序(如GRUB)的配置文件。
6.更新GRUB配置文件运行update-grub命令来更新GRUB引导加载程序的配置文件。
这将确保新安装的内核在下次启动时可用。
7.重启系统安装完成后,通过重启系统来加载新的内核和模块。
在系统启动时,GRUB将显示一个菜单,你可以选择要启动的内核版本。
8.加载和卸载内核模块现在,你可以使用insmod命令来加载内核模块。
例如,运行insmod hello.ko命令来加载名为hello.ko的模块。
加载的模块位于/lib/modules/<kernel-version>/目录下。
如果你想卸载一个已加载的内核模块,可以使用rmmod命令。
例如,运行rmmod hello命令来卸载已加载的hello模块。
9.编写和编译模块代码要编写一个内核模块,你需要创建一个C文件,包含必要的模块代码。
嵌入式linux内核下串行驱动程序的实现
型 设 备驱 动 的一 般 方 法 。
其 中 的 c c a ) 指 字 符设 备 , 面 的 第 (h r 是 后
一
个 数 字 ( 2 ) 一 个 未 被 使 用 的 主 设 备号 , 11是
收 稿 日期 : 1 帅 2—0 6一1 3
A b t ac Ba i on e s s r t: s c c c pt of de c drve a I s nt r pt vie i r nd t i er u han i i Li dlng n nux r a e i r nt oduc d.W ih a U A RT s drv r i e t i e n uClnu s a xa p e, h i pl m e aton of i x a n e m l t e m e nt i s ra drv i d t ie e il i er s e al d, w hih a be go r f r nc t o her e i l c c n a od e e e e o t s ra dr v r ie de e opm en n Li vl t i nux. Ke y wor s: d Em b de nu ; rv r Bo t ed d Li x D i e t om —hal ;Fa k q ue; i g Bu f r f s ue R n fe
・
产 品 , 格 昂 贵 , 且 各 自 的 源 代 码 又 不 公 价 并
开 , 得 每 个 系 统 上 的 应 用 软 件 与 其 它 系 统 使 都 无 法 兼 容 。这 种 封 闭性 同 时还 导 致 了商 业
嵌 入 式 系统 在 对 各 种 设 备 的 支 持 方 面 也 存 码等 特 征 , 得 L n x在 近 两 年 开 使 iu
基于linux2.6内核的字符设备驱动程序设计
, sr t t uc
o ne = TH I M ODULE; w r S
… … … … …
//获 取 字 符 设 备 号
2ce d v结构体
存 ln x2. 核 中 , 用 c e 结 构 体 描 iu 6内 使 dv 述 一 个 字 符 设 备 , d v结 构 体 定 义 如 下 : ce
.
_
的 结构 体 , 中 包 含设 备所 涉 及 到 的c e 其 d v、 私有数据及信号量等信息 。 下所 示: 如 / 设 备 结 构 体 /
sr t t uc XXX—d v t —e
— —
{, i e t l f t ) sz , of ;
_ —
f
s r t de c v; t uc c v de
s ie sz
— —
ቤተ መጻሕፍቲ ባይዱ
/ /从 设 备 中 同 步 读 取 数 据 t* ie(tutf e} h r u r (wrt) rc i .c a s s l e
—
}, i e t, l f t { ; sz of )
//该 设 备 其 他 的 私 有 数 据 和 信 号 量 的 信 息 的 定 义
… …
/ /向 设 备 发 送 数 据 u sg e n * o1 sr c i ,src n in d it(p l(tutfl ) e十 tu t
p l t bl sr c ) o l a e tu t :
—
}X —e X X d v; 模 块 加 载 和 卸 载 函数 的 形 式 如 下 : /+ 备 驱 动 模 块 加 载 函 数 / 设
・
计 算机技 术 ・
基 于 l u 26 i x . 内核 的字 符 设 备驱 动程 序设 计 n
linux驱动启动顺序
linux驱动启动顺序⾸先,我们可以查看Linux内核编译完成后的System.map⽂件,在这个⽂件中我们可以看到macb(dm9161驱动模块)链接到了dm9000驱动之前,如下所⽰:c03b6d40 t __initcall_tun_init6c03b6d44 t __initcall_macb_init6c03b6d48 t __initcall_dm9000_init6c03b6d4c t __initcall_ppp_init6c03b6d50 t __initcall_ppp_async_init6我尝试修改arch/arm/mach-at91/board-sam9260ek.c中DM9000和DM916设备添加的顺序,即先添加 dm9000,后添加dm9161。
编译后运⾏发现,结果还是⼀样。
⾃⼰想了想,这也在情理之中。
因为这个出现这个问题的主要原因是这两个驱动加载的先后顺序,⽽不是设备添加的先后顺序。
在Linux内核中维护着两个链,⼀个设备链,⼀个驱动链,他们两个就像情侣⼀样互相依赖,互相纠缠在⼀起的。
当我们新添加⼀个设备时,他会被加⼊到设备链上,这时内核这个红娘会就会到驱动链上给他找他的另外⼀半(驱动),看是否有哪个驱动看上了他(这个驱动是否⽀持这个设备),如果找到了这个驱动,那么设备就能够使⽤(⼤家纠缠到⼀块了,该⼲嘛就⼲嘛去了)。
⽽如果没有找到,那么设备就只能默默地在那⾥等待他的另⼀半的出现。
下⾯是arch/arm/mach-at91/board-sam9260ek.c添加设备的代码:static void __init ek_board_init(void){ /* Serial */at91_add_device_serial(); /* USB Host */at91_add_device_usbh(&ek_usbh_data); /* USB Device */at91_add_device_udc(&ek_udc_data); /* SPI */at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices)); /* NAND */ek_add_device_nand(); /* Ethernet */ ek_add_device_dm9000(); /* Add dm9000 driver by guowenxue, 2012.04.11 */at91_add_device_eth(&ek_macb_data); /* MMC */at91_add_device_mmc(0, &ek_mmc_data); /* I2C */at91_add_device_i2c(ek_i2c_devices, ARRAY_SIZE(ek_i2c_devices)); /* SSC (to AT73C213) */#if defined(CONFIG_SND_AT73C213) || defined(CONFIG_SND_AT73C213_MODULE)at73c213_set_clk(&at73c213_data); /* Modify by guowenxue, 2012.04.11 */#endifat91_add_device_ssc(AT91SAM9260_ID_SSC, ATMEL_SSC_TX);#if 0 /* comment by guowenxue */ /* LEDs */at91_gpio_leds(ek_leds, ARRAY_SIZE(ek_leds)); /* Push Buttons */ek_add_device_buttons();#endif}MACHINE_START(AT91SAM9260EK, "Atmel AT91SAM9260-EK") /* Maintainer: Atmel */.timer = &at91sam926x_timer,.map_io = at91_map_io,.init_early = ek_init_early,.init_irq = at91_init_irq_default,.init_machine = ek_board_init,MACHINE_ENDMACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(".init"),是初始化数据,Kernel 起来之后将被丢弃。
