• 一、硬件体系结构简介

    运行嵌入式linux的硬件平台主要包括如下几个部分:cpu(ARMv4指令集兼容)、uart、memory controller、定时器、flash存储器,sdram存储器,中断控制器和DMA。

    二、编译环境和编译工具

    linux操作系统源码绝大部分是用c语言开发的,有一些与硬件直接相关的代码则用特定于某一CPU体系结构的汇编来实现。这些源码只能用GNU的gcc编译工具来进行编译、链接。

    GNU gcc可以运行于Linux/Unix操作系统上。如果要在Windows平台上运行gcc,则必须安装Cygwin。Cygwin可以在Windows中安装一个linux的运行环境,这样就可以在windows下运行原本只能在linux下运行的程序。

    为了在PC上编译得到运行于目标CPU上的操作系统内核,还必须安装一个合适的交叉编译器。Gcc 提供了现成的针对MIPS、ARM、M68K、Sharc、PowerPC的交叉编译器。如果没有现成的交叉编译器,则需要自行设计。GNU网站提供了一些如何开发新的交叉编译器的文章。开发一个新的编译器,一般需要如下几个步骤:

    1、编写机器描述脚本。采用gcc的RTL(Register Tansfer Language)语言描述针对某一CPU体系结构的机器指令与寻址方式、CPU浮点处理方式、endianess、c语言中各种数据类型的位宽、寄存器的个数和使用规则、堆栈和函数调用规则等体系结构的细节。
    2、设计代码生成器。Gcc在对c语言源文件进行了词法和语法分析后,将产生一种中间格式文件(intermediate representation)。为了把这种中间格式文件转化为针对具体CPU体系结构的机器码,需要自行设计一个代码生成器。
    3、设计汇编器
    4、设计链接器

    三、系统代码的修改

    需要修改的系统源码主要有如下几处:
    (1) bootloader相关代码。此代码位于linux-2.4.x\arch\armnommu\boot\compressed\目录下名为head.s的文件中。此处程序用汇编语言实现,需要修改的地方主要是设置memory map的代码,与memory controller的硬件实现相关。
    (2) UART相关代码。UART相关代码位于\linux-2.4.x\drivers\char目录下的serial.c
    (3) 定时器相关代码。linux中有如下函数调用star_kernel()->time_init()->setup_timer(),需要修改setup_timer()函数中的相关代码。
    (4) 中断控制器相关。\linux-2.4.x\arch\armnommu\irq.c

    除了上述的代码,还有其他多处需要修改。在修改源代码时,可按照uclinux启动和执行顺序依次修改整个平台。熟悉linux内核源码结构对操作系统移植有很大帮助。
  • 如何在嵌入式LINUX中增加自己的设备驱动程序

    驱动程序的使用可以按照两种方式编译,一种是静态编译进内核,另一种是编译成模块以供动态加载。由于uClinux不支持模块动态加载,而且嵌入式LINUX不能够象桌面LINUX那样灵活的使用insmod/rmmod加载卸载设备驱动程序,因而这里只介绍将设备驱动程序静态编译进uClinux内核的方法。
    下面以UCLINUX为例,介绍在一个以模块方式出现的驱动程序test.c基础之上,将其编译进内核的一系列步骤:

    (1) 改动test.c源带代码
    第一步,将原来的:
    #include
    #include
    char kernel_version[]=UTS_RELEASE;
    改动为:
    #ifdef MODULE
    #include
    #include
    char kernel_version[]=UTS_RELEASE;
    #else
    #define MOD_INC_USE_COUNT
    #define MOD_DEC_USE_COUNT
    #endif
    第二步,新建函数int init_test(void)
    将设备注册写在此处:
    result=register_chrdev(254,"test",&test_fops);


    (2) 将test.c复制到/uclinux/linux/drivers/char目录下,并且在/uclinux/linux/drivers/char目录下mem.c中,int chr_dev_init( )函数中增加如下代码:
    #ifdef CONFIG_TESTDRIVE
    init_test();
    #endif

    (3) 在/uclinux/linux/drivers/char目录下Makefile中增加如下代码:
    ifeq($(CONFIG_TESTDRIVE),y)
    L_OBJS+=test.o
    Endif

    (4) 在/uclinux/linux/arch/m68knommu目录下config.in中字符设备段里增加如下代码:
    bool support for testdrive CONFIG_TESTDRIVE y

    (5) 运行make menuconfig(在menuconfig的字符设备选项里你可以看见我们刚刚添加的support for testdrive选项,并且已经被选中);make dep;make linux;make linux.text;make linux.data;cat linux.text linux.data > linux.bin。
    (6) 在 /uclinux/romdisk/romdisk/dev/目录下创建设备:
    mknod test c 254 0
    并且在/uclinux/appsrc/下运行make,生成新的Romdisk.s19文件。

    到这里,在UCLINUX中增加设备驱动程序的工作可以说是完成了,只要将新的linux.bin与Romdisk.s19烧入目标板中,你就可以使用自己的新设备test了。
  • 其实 Linux 下的声音设备编程比大多数人想象的要简单得多。一般说来,我们常用的声音设备是内部扬声器和声卡,它们都对应 /dev 目录下的一个或多个设备文件,我们象打开普通文件一样打开它们,用 ioctl()函数设置一些参数,然后对这些打开的特殊文件进写操作。
      由于这些文件不是普通的文件,所以我们不能用 ANSI C(标准C)的 fopen、fclose 等来操作文件,而应该使用系统文件 I/O 处理函数(open、read、write、lseek 和 close)来处理这些设备文件。ioctl()或许是 Linux 下最庞杂的函数,它可以控制各种文件的属性,在 Linux 声音设备编程中,最重要的就是使用此函数正确设置必要的参数。
      下面我们举两个实际的例子来说明如何实现 Linux 下的声音编程。由于此类编程涉及到系统设备的读写,所以,很多时候需要你有 root 权限,如果你将下面的例子编译后不能正确执行,那么,首先请你检查是否是因为没有操纵某个设备的权限。

    1. 对内部扬声器编程
      内部扬声器是控制台的一部分,所以它对应的设备文件为 /dev/console。变量 KIOCSOUND 在头文件 /usr /include /linux /kd.h 中声明,ioctl 函数使用它可以来控制扬声器的发声,使用规则为:
      ioctl ( fd, KIOCSOUND, (int) tone);
      fd 为文件设备号,tone 是音频值。当 tone 为 0 时,终止发声。必须一提的是它所理解的音频和我们平常以为的音频是不同的,由于计算机主板定时器的时钟频率为 1.19MHZ,所以要进行正确的发声,必须进行如下的转换:
      扬声器音频值 = 1190000/ 我们期望的音频值。
      扬声器发声时间的长短我们通过函数 usleep(unsigned long usec)来控制。它是在头文件 /usr /include /unistd.h 中定义的,让程序睡眠 usec 微秒。下面即是让扬声器按指定的长度和音频发声的程序的完整清单:

    #include < fcntl.h >
    #include < stdio.h >
    #include < stdlib.h >
    #include < string.h >
    #include < unistd.h >
    #include < sys/ioctl.h >
    #include < sys/types.h >
    #include < linux/kd.h >

    /* 设定默认值 */
    #define DEFAULT_FREQ 440 /* 设定一个合适的频率 */
    #define DEFAULT_LENGTH 200 /* 200 微秒,发声的长度是以微秒为单位的*/
    #define DEFAULT_REPS 1 /* 默认不重复发声 */
    #define DEFAULT_DELAY 100 /* 同样以微秒为单位*/

    /* 定义一个结构,存储所需的数据*/
    typedef struct {
    int freq; /* 我们期望输出的频率,单位为Hz */
    int length; /* 发声长度,以微秒为单位*/
    int reps; /* 重复的次数*/
    int delay; /* 两次发声间隔,以微秒为单位*/
    } beep_parms_t;


    /* 打印帮助信息并退出*/
    void usage_bail ( const char *executable_name ) {
    printf ( "Usage: \n \t%s [-f frequency] [-l length] [-r reps] [-d delay] \n ",
    executable_name );
    exit(1);
    }

    / * 分析运行参数,各项意义如下:
    * "-f <以 HZ 为单位的频率值 >"
    * "-l <以毫秒为单位的发声时长 >"
    * "-r <重复次数 >"
    * "-d <以毫秒为单位的间歇时长 >"
    */
    void parse_command_line(char **argv, beep_parms_t *result) {
    char *arg0 = *(argv++);
    while ( *argv ) {
    if ( !strcmp( *argv,"-f" )) { /*频率*/
    int freq = atoi ( *( ++argv ) );
    if ( ( freq <= 0 ) | | ( freq > 10000 ) ) {
    fprintf ( stderr, "Bad parameter: frequency must be from 1..10000\n" );
    exit (1) ;
    } else {
    result->freq = freq;
    argv++;
    }
    } else if ( ! strcmp ( *argv, "-l" ) ) { /*时长*/
    int length = atoi ( *(++argv ) );
    if (length < 0) {
    fprintf(stderr, "Bad parameter: length must be >= 0\n");
    exit(1);
    } else {
    result->length = length;
    argv++;
    }
    } else if (!strcmp(*argv, "-r")) { /*重复次数*/
    int reps = atoi(*(++argv));
    if (reps < 0) {
    fprintf(stderr, "Bad parameter: reps must be >= 0\n");
    exit(1);
    } else {
    result->reps = reps;
    argv++;
    }
    } else if (!strcmp(*argv, "-d")) { /* 延时 */
    int delay = atoi(*(++argv));
    if (delay < 0) {
    fprintf(stderr, "Bad parameter: delay must be >= 0\n");
    exit(1);
    } else {
    result->delay = delay;
    argv++;
    }
    } else {
    fprintf(stderr, "Bad parameter: %s\n", *argv);
    usage_bail(arg0);
    }
    }
    }

    int main(int argc, char **argv) {
    int console_fd;
    int i; /* 循环计数器 */
    /* 设发声参数为默认值*/
    beep_parms_t parms = {DEFAULT_FREQ, DEFAULT_LENGTH, DEFAULT_REPS,
    DEFAULT_DELAY};
    /* 分析参数,可能的话更新发声参数*/
    parse_command_line(argv, &parms);

    /* 打开控制台,失败则结束程序*/
    if ( ( console_fd = open ( "/dev/console", O_WRONLY ) ) == -1 ) {
    fprintf(stderr, "Failed to open console.\n");
    perror("open");
    exit(1)
  • 工作需要写了我们公司一块网卡的Linux驱动程序。经历一个从无到有的过程,深感技术交流的重要。Linux作为挑战微软垄断的强有力武器,日益受到大家的喜爱。真希望她能在中国迅速成长。把程序文档贴出来,希望和大家探讨Linux技术和应用,促进Linux在中国的普及。

    Linux操作系统网络驱动程序编写
    一.Linux系统设备驱动程序概述
    1.1 Linux设备驱动程序分类
    1.2 编写驱动程序的一些基本概念
    二.Linux系统网络设备驱动程序
    2.1 网络驱动程序的结构
    2.2 网络驱动程序的基本方法
    2.3 网络驱动程序中用到的数据结构
    2.4 常用的系统支持
    三.编写Linux网络驱动程序中可能遇到的问题
    3.1 中断共享
    3.2 硬件发送忙时的处理
    3.3 流量控制(flow control)
    3.4 调试
    四.进一步的阅读
    五.杂项




    一.Linux系统设备驱动程序概述
    1.1 Linux设备驱动程序分类
    Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。在2.0.xx到2.2.xx的变动里,驱动程序的编写做了一些改变,但是从2.0.xx的驱动到2.2.xx的移植只需做少量的工作。
    Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network 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()传递给上层处理。