java聊天室软件开发:uClinux学习笔记

来源:百度文库 编辑:中财网 时间:2024/05/01 14:42:59

uClinux学习笔记

Software

uClinux学习笔记

陈华:
目录 [显示隐藏]
1 目的
2 背景知识
3 share library
4 可执行文件格式
5 内存分配
6 进程
7 共享库
8 查看内存情况
[编辑]
目的

我整理的一部分的uClinux的资料供大家参考、对系统理解的越多越有利于我们的工作、即使只是一般的业务开发最好也了解一下。

uClinux与普通linux的最大区别在于没有VM、衍生出来的不同点都是和这部分相关的、由于是设计给没有MMU的嵌入式CPU设计的、所以VM的实现变成不可能。

最大的危险在于、由于没有VM的内存保护、任何的进程都可以修改其他进程乃至核心的数据。


[编辑]
背景知识


Unix 进程的内存使用可以分为5个部分:text, bss, data, heap, stack text 代码 bss 未初始化数据短 data 初始化数据段 heap 堆空间,libc管理用于动态分配内存(malloc) stack 栈空间,主要用于参数传递

其中stack在地址空间的最高部分,自高而低的增长,heap在其他段的最下方,自低而高的增长。stack的增长是自动的,OS会管理,heap的增长是由libc向OS申请的。

heap 管理的经典算法是power-2, 该算法以2的幂的单位进行内存分配,例如32,64,128,265...,同时配合伙伴算法进行碎片的恢复。这种算法的一个问题是会导致一些内存浪费,这种浪费被叫做slack memroy。最最恶劣的情况下可能有接近50%的内存没有被使用。

在核心中还引入了一种分配机制 Slab,专门用于分配对象型数据,所谓的对象型数据一般形式为一个struct,其中的很多member再分配后会被进行初始化,而且在释放的时候后常常有一些member保持不变,为了减少初始化的代价和power-2算法的slack,引入Slab概念,Slab申请整块的页面划分成一个一个指定的大小,使用用户提供的函数在分配的时候进行初始化和释放,有效的提高了速度,在被Solaris引入后被广泛的使用了。


[编辑]
share library


请注意unix系统中使用share library,而不是dll. 这不是一个起名字时的不同,而是具有本质区别的。share library包含了在磁盘上和在内存中都被share。而MS的dll实际上一般只能在磁盘上share,内存中的share很困难,因此他只是一个动态连接而不是共享。
[编辑]
可执行文件格式

standard linux使用的elf格式的文件、这种文件具有PIC(position independent code)特性、表明他是不依赖于内存地址的.

uClinux使用的是FLAT格式、是一种紧凑的格式、仅仅包含code和data、允许重定位.

重定位的基本原理是,不采用绝对调用,所有的不可确定的调用利用中间变量来进行保存,这些中间变量有的在启动的时候有OS来处理,有的被libc的runtime在动态连接的时候进行处理。

flthdr chmod

chmod

Magic: bFLT
Rev: 4
Entry: 0x50
Data Start: 0x1c920
Data End: 0x21200
BSS End: 0x26ba0
Stack Size: 0x4e20
Reloc Start: 0x21200
Reloc Count: 0x6e0
Flags: 0x1 ( Load-to-Ram )

可以清楚的看到data,bss,stack的大小,其中的reloc表示relocation, 就是重定位使用的中间数据,这些中间数据由kernel负责初始化。
[编辑]
内存分配

这里仅仅描述user space的内存管理

内存分配很痛苦、因为没有vm、导致不能动态的分配stack和heap、实际上uClinux是没有传统heap的。stack的大小是可以在编译期间指定的、一旦被加载其大小不可改变。

malloc、 free的实现是基于uClinux kernel维护的一个大的free memory pool。相当于全部的进程在共享同一个heap。优点是所有被free的内存立刻回到memory pool、可以被其他的进程使用。缺点的碎片可能会使你在分配内存的时候失败。因为没有VM碎片的整理是不可能的。因此、我们在coding的时候要对此有充分的考虑。uClinux使用mmap来映射实现 malloc、使用mmap是有一定代价的、每次分配大约有56个字节的管理成本。

因为没有VM、uClinux不能试图在内存中移动数据、可能导致明明看起来有足够的内存、但却不能加载程序。
[编辑]
进程

fork 的问题、fork是没有被实现的、只能使用vfork。fork的实现机制是copy-on-write。因为在uClinux里面没有VM、copy- on-write就不存在了。使用vfork之后主进程就会被suspend起来、让子进程继续执行、直到子进程调用了exit或者exec之后内存才会被释放。

而且请特别注意、vfork的机制里面子进程和父进程是使用同一段内存的、这意味着子进程对变量的修改对于父进程是可见的。
[编辑]
共享库

linux 共享库的目的在于减少内存和硬盘的空间消耗、核心会保证每个共享库的代码段在内存中只有一份、通过VM将共享库的代码映射到使用他的进程内存空间中去、而且、核心会根据进程的情况来决定使用他的哪一部分内存地址。这些的动作是由核心完成的、开发人员对此基本上不需要了解。

因为没有VM、最初的uClinux里面是没有共享库的。

一直到uClinux XIP and shared lib patches的出现。

XIP 是execute in position, 当使用XIP的时候OS仅仅为application分配data,bss,stack,而text就是用rom中的。通过这样的方式减少了对内存的消耗。实际上XIP是设计给nor flash使用的, 因为nor flash被简单的映射到内存空间中来,因此,这个工作相对比较简单。在使用人ramdisk的时候理论上也是可以XIP的,但是由于系统设计的模块化,这个工作变得很复杂。

目前的XIP支持仅仅限于romfs,因为romfs是唯一可以确保文件连续性的fs,而且他是只读的,进一步简化了工作。

当使用XIP的时候share library自动的被支持了,因为存贮即使执行的位置,他们的地址绝对不会冲突。

不使用XIP的时候,也可以支持share library. uClinux中的share library结构不复杂而且有很多的限制。

其基本原理如下:使用一个静态数据段,程序和share lib一起使用。每一个share库都被分配一个唯一的数字编号,该编号是有上限的,通过全局静态区可以访问到一个数组,share lib的编号就是数组的下标。通过arm的Static Base Register,每一个函数都可以都可以找到静态区查讯数据。
[编辑]
查看内存情况

mem_map 是linux内部的变量名,用于管理所有的page,每个page的大小为4K,在下面的内容中,每一个字母表示一个page的使用情况

cat /proc/mem_map

0x0c000000: RRRRRRRRssss1111ss1RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c040000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c080000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c0c0000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c100000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c140000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c180000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c1c0000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c200000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c240000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c280000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c2c0000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c300000: RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
0x0c340000: RRs1[]1ssssss1[]ss[]ss[][][][][]sss1[][][]ssssssssssssssss[]ssss
0x0c380000: sssssssss1[]11sssX[]ssXsXXXs[]XXXsXXXXXXXXXXXXXXXXXXXXXXXX[]ss1s
0x0c3c0000: s1sss--11-ssss-ss-[][]sss-s--1ss[][][]-----sssss----------------
0x0c400000: -------s11---s--------------------------------------------------
0x0c440000: ----------------------------------------------------------------
0x0c480000: ----------------------------------------------------------------
0x0c4c0000: ----------------------------------------------------------------
0x0c500000: ----------------------------------------------------------------
0x0c540000: ----------------------------------------------------------------
0x0c580000: ----------------------------------------------------------------
0x0c5c0000: ----------------------------------------------------------------
0x0c600000: ----------------------------------------------------------------
0x0c640000: ----------------------------------------------------[===========
0x0c680000: ===============================]------------[==================]
0x0c6c0000: ------------[============][===================]---------[=======
0x0c700000: ==]-[==========================][==================][===========
0x0c740000: =======================================================]----[===
0x0c780000: ======]-[==========]------[=][==================================
0x0c7c0000: ===============================================================]

init Mem: 125152 Slack: 80 Shared: 0
inetd Mem: 55840 Slack: 771 Shared: 0
portmap Mem: 115680 Slack: 5144 Shared: 0
telnetd Mem: 72256 Slack: 1747 Shared: 0
sh Mem: 97952 Slack: 566 Shared: 0
sh Mem: 92384 Slack: 549 Shared: 0
cat Mem: 191136 Slack: 1401 Shared: 0
Total Mem: 750400 Slack: 10258 Shared: 0

Active: 5, inactive: 27, free: 698
Free pages: 698 (2792kB), %2 frag, %1 slack
Free blks: 17 min=1 max=614 avg=41
Used blks: 18 min=1 max=965 avg=75

R : 保留页面 B : 块设备 X : 映射内存 s : Slab 1 : 被分配的1个页面 = : 被分配的连续的页面

cat /proc/slabinfo

cache-name | num-active-objs | total-objs | object size | num-active-slabs | total-slabs | num-pages-per-slab

slabinfo - version: 1.1 kmem_cache 57 68 112 2 2 1 tcp_tw_bucket 0 0 96 0 0 1

Linux下的多线程编程

Software

1 引言
  线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。
  为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
  下面我们先来尝试编写一个简单的多线程程序。

2 简单的多线程编程
  Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序 example1.c。

/* example.c*/
#include
#include
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread.\n");
}

int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!\n");
exit (1);
}
for(i=0;i<3;i++)
printf("This is the main process.\n");
pthread_join(id,NULL);
return (0);
}

我们编译此程序:
gcc example1.c -lpthread -o example1
运行example1,我们得到如下结果:
This is the main process.
This is a pthread.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.
再次运行,我们可能得到如下结果:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.

  前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用到了两个函数,  pthread_create和pthread_join,并声明了一个pthread_t型的变量。
  pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
  typedef unsigned long int pthread_t;
  它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:
  extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
  void *(*__start_routine) (void *), void *__arg));
  第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
  函数pthread_join用来等待一个线程的结束。函数原型为:
  extern int pthread_join __P ((pthread_t __th, void **__thread_return));
  第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:
  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
  唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
  在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。

3 修改线程的属性
  在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
  属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
  关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
  设置线程绑定状态的函数为 pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值: PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。
#include
pthread_attr_t attr;
pthread_t tid;

/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
  另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数 pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。
#include
#include
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;

pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, ?m);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, ?m);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
  
4 线程的数据处理
  和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面,我们就逐步介绍处理线程数据时的有关知识。

4.1 线程数据
  在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在 A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
  和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
  创建键的函数原型为:
  extern int pthread_key_create __P ((pthread_key_t *__key,
  void (*__destr_function) (void *)));
  第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。

  在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数 createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。
/* 声明一个键*/
pthread_key_t myWinKey;
/* 函数 createWindow */
void createWindow ( void ) {
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 调用函数createMyKey,创建键*/
pthread_once ( & once, createMyKey) ;
/*win指向一个新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
setWindow(win);
/* 将窗口指针值绑定在键myWinKey上*/
pthread_setpecific ( myWinKey, win);
}

/* 函数 createMyKey,创建一个键,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}

/* 函数 freeWinKey,释放空间*/
void freeWinKey ( Fl_Window * win){
delete win;
}

  这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数 pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
  extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
  extern void *pthread_getspecific __P ((pthread_key_t __key));
  这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。

4.2 互斥锁
  互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
  我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

void reader_function ( void );
void writer_function ( void );

char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}

void writer_function (void){
while(1){
/* 锁定互斥锁*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
  这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数 pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数 pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数 pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值, PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、 PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
   pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。
  上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数 pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

4.3 条件变量
  前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
  条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
  extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
  其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构 pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是 PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。 
  函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:
  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));
  线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数 pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
  另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));
  它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
  函数pthread_cond_signal()的原型为:
  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
  它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock (&count_lock);
}

increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}
  count值为0时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数 increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。
  函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。

4.4 信号量
  信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件 /usr/include/semaphore.h中定义。
  信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
  函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
  函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
  函数sem_destroy(sem_t *sem)用来释放信号量sem。
  下面我们来看一个使用信号量的例子。在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
/* File sem.c */
#include
#include
#include
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 从文件1.dat读取数据,每读一次,信号量加一*/
void ReadData1(void){
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[0],&stack[1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*从文件2.dat读取数据*/
void ReadData2(void){
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[0],&stack[1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/
void HandleData1(void){
while(1){
sem_wait(&sem);
printf("Plus:%d+%d=%d\n",stack[0],stack[1],
stack[0]+stack[1]);
--size;
}
}

void HandleData2(void){
while(1){
sem_wait(&sem);
printf("Multiply:%d*%d=%d\n",stack[0],stack[1],
stack[0]*stack[1]);
--size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void *)HandleData1,NULL);
pthread_create(&t2,NULL,(void *)HandleData2,NULL);
pthread_create(&t3,NULL,(void *)ReadData1,NULL);
pthread_create(&t4,NULL,(void *)ReadData2,NULL);
/* 防止程序过早退出,让它在此无限期等待*/
pthread_join(t1,NULL);
}

  在Linux下,我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem。我们事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我们运行sem,得到如下的结果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11

  从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这也往往是多线程编程要注意的问题。

5 小结
  多线程编程是一个很有意思也很有用的技术,使用多线程技术的网络蚂蚁是目前最常用的下载工具之一,使用多线程技术的grep比单线程的grep要快上几倍,类似的例子还有很多。希望大家能用多线程技术写出高效实用的好程序来。
出处 http://www.fanqiang.com

堆和栈的区别

Software

堆(heap)和栈(stack)有什么区别??

简单的可以理解为:
heap:是由malloc之类函数分配的空间所在地。地址是由低向高增长的。
stack:是自动分配变量,以及函数调用的时候所使用的一些空间。地址是由高向低减少的。

预备知识—程序的内存分配

一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}


二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。
2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度, 也最灵活
2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
2.6存取效率的比较

char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指edx中,在根据edx读取字符,显然慢了。


2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

堆和栈的区别主要分:
操作系统方面的堆和栈,如上面说的那些,不多说了。
还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。
虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因针值读

ARM嵌入式系统中断向量表的动态配置

Software

作 者:■ 江西理工大学 吴君钦 谢斌 任克强

摘要:通常32位ARM嵌入式系统的中断向量表是在程序编译前设置好的,每次编写中断程序都要改C程序的汇编启动代码,相当繁琐。本文给出一种配置ARM中断向量表新方法。该方法比通常方法仅增加一条指令执行时间,简便高效,功能完备,向量表在运行时动态生成,C程序可以使用固定向量表的启动代码,并可隐藏起来。


关键词:动态配置 嵌入式系统 ARM 中断向量表

引 言

  一般32位ARM嵌入式系统的中断向量表是程序编译前设置好的。在编写32位ARM嵌入式系统的中断服务程序、设置和修改ARM体系结构的中断向量表时,常感到相当麻烦,不得不修改汇编代码,对不喜欢使用汇编代码编程的程序员尤其如此。当需要在程序运行过程中动态修改中断向量的程序时会感到更为不便,不得不增加很多分支处理指令才能实现。为此本文提出一种简便高效的配置方法,实现了ROM固化程序在运行时动态配置ARM嵌入式系统中断向量表的功能。

1 ARM中断向量两种设置方法

  在32 位ARM系统中 ,一般都是在中断向量表中放置一条分支指令或PC寄存器加载指令,实现程序跳转到中断服务例程的功能。例如:
IRQEntry B HandleIRQ ;跳转范围较小
B HandleFIQ ;

IRQEntry LDR PC,=HandleIRQ ;跳转的范围是任意32位地
;址空间
LDR PC,=HandleFIQ

  LDR伪指令等效生成1条存储读取指令和1条32位常数定义指令。32位常数存储在LDR指令附近的存储单元中,相对偏移小于4KB。该32位数据就是要跳转到的中断服务程序入口地址。

  之所以使用LDR伪指令,是因为ARM的RISC指令为单字指令,不能装载32位的立即数(常数),无法直接把一个32位常数数据或地址数据装载到寄存器中。下面一段程序与上述伪指令功能等效,但中断向量表描述得更为清晰。其中VectorTable为相对LDR指令的偏移量:
IRQEntry LDR PC,VectorTable+0
;与LDR PC,=HandleIRQ 等效
LDR PC,VectorTable+4
;与LDR PC,=HandleFIQ 等效
……
VectorTable DCD HandleIRQ
DCD HandleFIQ
……
HandleIRQ
……
HandleFIQ

  一般ARM嵌入式系统的程序都是固化在从00000000H开始的低端ROM空间中,中断向量表VectorTable也是固化在ROM中,所以上述两种方法都无法在程序运行时动态随机修改中断向量表。不论对于初学ARM处理器的程序员还是有经验的程序员,设置中断向量都相当繁琐,必须修改ARM 的C程序的启动代码。一段晦涩的汇编代码很不方便, 比较容易出错。

2 X86与ARM处理器中断向量表比较

  实模式X86程序员都熟悉, 在X86 体系结构的PC系统中,不论是用汇编还是用C语言,都可以动态随机地设置、修改中断向量表——只需简单地把中断服务例程的入口地址写入到中断向量表数据区,即可完成向量表的设置。

  X86向量表设置方便的原因有两个。其一是中断向量表与程序代码完全分离,中断向量表设置在RAM数据空间,向量表存放的数据是纯粹地址数据;而在 ARM向量表中存放的是与中断服务例程入口有关的一条分支指令。另一个原因是,除BIOS外,大多数PC程序都是在运行时加载到RAM中的,程序数据是不加区别的,所以可以很容易在程序运行的过程中从数据生成程序,并可以很容易把CPU控制权转到新生成的程序中。

  表面上看,在ARM第二种中断向量设置方法的向量表VectorTable中也是纯地址数据,不含指令代码,似乎可以把VectorTable设置在RAM数据段中。然而一般 ARM体系的ROM代码段和RAM数据段间的偏移远大于212, 故超出了LDR使用PC为基址的相对寻址范围。

  代码中的 VectorTable是一个与当前PC间的一个偏移,LDR指令的相对地址是在编译时计算的,要求VectorTable < 212 ,所以VectorTable不能随意安排在RAM空间中。VectorTable一般只能安排在中断跳转指令附近的代码区内中。

3 ARM结构中中断向量表的动态配置方法

  要在ARM结构中实现与X86中一样方便的中断向量的随机存取功能,向量表的地址数据必须可以安排在任意32位地址的RAM空间中。为此,中断处理必须增加一条指令,先跳转到向量表,然后执行向量表中动态生成的跳转指令,跳转到中断服务程序,参见下列初始化代码:
;******向量表******
ENTRY ;原向量偏移 ,中断号
B ResetHandle ;0x00 ,00
LDR PC,=NewVectorTable+0x08 ;0x04 ,未定义 ,01
LDR PC,=NewVectorTable+0x10 ;0x08 ,SWI ,02
LDR PC,=NewVectorTable+0x18 ;0x0c ,未定义 ,03
LDR PC,=NewVectorTable+0x20 ;0x10 ,未定义 ,04
LDR PC,=NewVectorTable+0x28 ;0x14 ,未定义 ,05
LDR PC,=NewVectorTable+0x30 ;0x18 , IRQ ,06
LDR PC,=NewVectorTable+0x38 ;0x1c , FIQ ,07
……
;******代码段******
ResetHandle
……
;***数据段,为NewVectorTable分配数据空间***
NewVectorTable # 128 ; 大小根据需要定义,每向量2个字(8字节);
程序运行时,中断服务的初始化程序必须设置好新的中断向量表,即在NewVectorTable表中动态生成下列指令:
NewVectorTable ;表安排在RAM顶端0x0c1fff00处(由硬件设定)
LDR PC,[PC,#4] ;指令代码为0xe51ff004,功能为PC
;<-[PC+4]
nVt00 DCD ISR_RESET_HANDLE
LDR PC,[PC,#4] ;与LDR PC,nVt01指令等效
nVt01 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
nVt02 DCD ISR_SWI_HANDLE
LDR PC,[PC,#4]
nVt03 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
nVt04 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
nVt05 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
nVt06 DCD ISR_IRQ_HANDLE
LDR PC,[PC,#4]
nVt07 DCD ISR_FIQ_HANDLE
……
可用C函数在NewVectorTable中生成含上述指令的向量表,具体实现如下:
#define VECTOR_TABLE 0x0c1fff00
//向量表首地址, 根据实际硬件来配置
#define INSTRUCTION_LDR_PC 0xe51ff004
//加载PC寄存器的指令码
//设置向量C函数 ,ISR_Handle中断服务程序地址
void SetVector(unsigned char no,unsigned long int ISR_Handle){
unsigned long int *pVectorTable;
//定义32位无符号数指针,指向向量表
pVectorTable =( (unsigned long int *)(VECTOR_TABLE+(no<<3)) ) ;
*pVectorTable++ = INSTRUCTION_LDR_PC;
// 在向量表中放置LDR PC,[PC,#4]指令
*pVectorTable = ISR_Handle;// 设置中断服务例程入口地址
}
//读取向量C函数 ,no代表中断号
unsigned long int GetVector(unsigned char no) {
unsigned long int *pVectorTable;
pVectorTable =( (unsigned long int *)(VECTOR_TABLE+(no<<3)) ) ;
return *(++pVectorTable) ; //返回中断处理程序入口地址
}

  使用上述初始化代码和向量设置函数,除复位向量外,其它所有中断向量都可以指向了在RAM数据区中的新向量表,并给定一个统一的中断编号。中断服务程序可以放在任何模块文件中编译连接,不需要修改原向量表代码,但在打开中断使用中断服务例程前必须使用C函数SetVector() 设置中断向量。

4 结 论

  本文提出的中断向量表配置策略和实现方法,简便高效,仅比标准处理方法增加一条指令的执行时间。当把ARM的C初始化汇编代码中所有中断源(包括扩展的内外部中断源)的向量都指向了新向量表,并统一编号,此后编写任何中断服务程序几乎不需要修改汇编代码,C初始化代码完全可以对C程序员隐藏起来,并可以像在X86体系下一样动态地设置和修改中断向量。

                 参考文献
1 Samsung Limited. S3C44B0X RISC Microprocessor Datasheet. 2001
2 马忠梅,马广云,等. ARM嵌入式处理器结构与应用基础. 北京:北京航空航天大学出版社,2002
3 ARM Limited. ARM7 TDMI Technical Reference Menual. ARM DDI 0210A,2001
4 杨延双,张晓冬, 等. 微机原理及汇编语言教程. 北京:北京航空航天大学出版社, 2002

Minigui IAL引擎实现

Software

2 如何在MiniGUI中添加新的IAL引擎:(假设新的IAL引擎为_NAME_IAL)

1. 在ial.c文件中添加新引擎的入口:

例如:(ial.c文件中)

A) #ifdef _NAME _IAL
#include "NAME.h"
#endif

B) 在input数组中添加

#ifdef _NAME _IAL
{"NAME ", InitNAMEInput, TermNAMEInput},
#endif

2. 把新的 .c 添加到 Makefile.am 文件中即可。

3. 修改配置文件IAL引擎项,使用这个新的IAL引擎_NAME_IAL.

2 如何编写IAL引擎:

1. 在MiniGUI中引入了输入抽象层(Input Abstract Layer ,IAL)的概念,它大大提高了MiniGUI的可移植性。

2. IAL是定义的一组不依赖于任何特殊硬件的抽象接口。而我们这里所说的IAL引擎则是对IAL定义的抽象接口的实现的底层代码。

3. IAL接口对下是IAL引擎,这些引擎使用了输入设备驱动提供的一些函数;对上则是应用程序或是GDI,上层MiniGUI库中ParseEvent()函数将通过这些IAL引擎将收集到的鼠标键盘事件转换为MiniGUI中的消息。

4. MiniGUI IAL结构如下:

在代码实现上,MiniGUI通过INPUT数据结构来表示输入引擎

typedef struct tagINPUT

{

char* id;

// Initialization and termination

BOOL (*init_input) (struct tagINPUT *input, const char* mdev, const char* mtype);

void (*term_input) (void);



// Mouse operations

int (*update_mouse) (void);

void (*get_mouse_xy) (int* x, int* y);

void (*set_mouse_xy) (int x, int y);

int (*get_mouse_button) (void);

void (*set_mouse_range) (int minx, int miny, int maxx, int maxy);

void (*suspend_mouse) (void);

int (*resume_mouse) (void);



// Keyboard operations

int (*update_keyboard) (void);

const char* (*get_keyboard_state) (void);

void (*suspend_keyboard) (void);

int (*resume_keyboard) (void);

void (*set_leds) (unsigned int leds);



// Event

#ifdef _LITE_VERSION

int (*wait_event) (int which, int maxfd, fd_set *in, fd_set *out, fd_set *except,

struct timeval *timeout);

#else

int (*wait_event) (int which, fd_set *in, fd_set *out, fd_set *except,

struct timeval *timeout);

#endif



char mdev [MAX_PATH + 1];

}INPUT;



extern INPUT* cur_input;



系统启动后,将根据配置文件寻找特定的输入引擎作为当前的输入引擎,并对全局变量cur_input(表当前使用的输入引擎)赋值。

为书写方便,定义了当前引擎的C语言宏。

#define IAL_InitInput (*cur_input->init_input)

#define IAL_TermInput (*cur_input->term_input)

#define IAL_UpdateMouse (*cur_input->update_mouse)

#define IAL_GetMouseXY (*cur_input->get_mouse_xy)

#define IAL_GetMouseButton (*cur_input->get_mouse_button)

#define IAL_SetMouseXY if (cur_input->set_mouse_xy) (*cur_input->set_mouse_xy)

#define IAL_SetMouseRange if (cur_input->set_mouse_range) (*cur_input->set_mouse_range)

#define IAL_SuspendMouse if (cur_input->suspend_mouse) (*cur_input->suspend_mouse)

#define IAL_UpdateKeyboard (*cur_input->update_keyboard)

#define IAL_GetKeyboardState (*cur_input->get_keyboard_state)

#define IAL_SuspendKeyboard if (cur_input->suspend_keyboard) (*cur_input->suspend_keyboard)

#define IAL_SetLeds(leds) if (cur_input->set_leds) (*cur_input->set_leds) (leds)



5. 我们编写IAL引擎要做的就是在_NAME_IAL.c中实现输入引擎结构中定义的这些函数。如:_NAME_IAL.c:

#include ………

…………..变量定义

/************************ Low Level Input Operations **********************/

mouse_update()

{

……………

}



mouse_getxy()

{

………………..

}

mouse_getbutton()

{

………………..

}

………….IAL引擎结构中定义的其它有关mouse的函数一般无需实现。

Keyboard_update()

{

……………..

}

keyboard_getstate()

{

………

}

……………..同样结构中定义的其它有关keyboard的函数一般无需实现。

Wait_event()

{

………………..

}

InitNAMEInput(INPUT* input, const char* mdev, const char* mtype)

{

…………

input->update_mouse = mouse_update;

input->get_mouse_xy = mouse_getxy;

input->set_mouse_xy = NULL;

input->get_mouse_button = mouse_getbutton;

input->set_mouse_range = NULL;



input->update_keyboard = keyboard_update;

input->get_keyboard_state = keyboard_getstate;

input->set_leds = NULL;



input->wait_event = wait_event;

………………..



}

void TermNAMEInput (void)

{

…………….

}

6. 各函数功能说明:开发一输入引擎一般也就是实现以下几个函数。

InitNAMEInput函数就是在src/ial/ial.c中定义的NAME输入引擎的初始化函数,它打开触摸屏(鼠标)和键盘设备文件。在成功打开这两个设备文件后,该函数设置了INPUT结构的其它一些成员。其中一些成员被赋值为NULL。

Mouse_update函数始终返回1,表明更新鼠标状态成功。

Mouse_getxy函数返回由其它函数准备好的鼠标位置,有可能做了适当的边界检查和支持屏幕显示旋转时对坐标的转换。函数的参数是两个指针变量。

Mouse_getbutton函数返回了触摸屏状态,即用户是否触摸了屏幕,相当于是否按下了左键。(或者鼠标哪个键按下,左键、右键还是当中的那个?)

返回值为触摸屏(鼠标)状态。

Keyboard_update函数根据其它函数准备好的键盘消息,适当填充了state数组。返回值是NR_KEYS。

Keyboard_state函数直接返回了state数组的地址:“return state”。

Wait_event函数是输入引擎的核心函数。这个函数首先将先前打开的两个设备的文件描述符与传入的in文件描述符集合并在了一起,然后调用了select系统调用。当select系统调用返回大于0的值时,该函数检查在两个文件描述符上是否有可读的数据等待读取,如果是,则分别从两个文件描述符读取触摸屏和按键数据。

返回值int型retvalue变量,其中包含了信息:鼠标事件发生、键盘事件发生或者鼠标和键盘事件都发生了(retvalue |= IAL_MOUSEEVENT,retvalue |= IAL_KEYEVENT)。

7. 开发IAL引擎,实现以上函数:

开发IAL引擎准备工作:需对输入设备有些了解:

a) 是鼠标、触摸屏还是触摸板?键盘都有哪些键?

b) 数据包格式怎样?(同样是触摸屏,不同型号其通过设备文件获得数据结构也不一样,得到的若是A/D转换得来的原始数据处理起来就有些麻烦;键盘的释放与按下状态是通过判断读得的字符最高位0与1来判断,还是对同一键其按下和释放对应两个毫无联系的字符)

c) 键盘的编码怎样,和MiniGUI在include/common.h中定义的一样吗?

d) 在读得的坐标和屏幕显示坐标间需不需要进行坐标转换?Mouse_getxy中最终返回的坐标为屏幕显示坐标。

e) 输入设备驱动支持select系统调用吗?

f) 触摸屏(触摸板)驱动中是否解决了抖动消除的问题,是否需要在IAL引擎中解决触摸屏消抖?

总结代码中几个针对具体系统开发的IAL 引擎:

a) 引擎包含的各个函数所要做的工作在上面的函数功能说明中已经反应出来,但是具体处理起来,不一定要求有的工作就得在某个函数中完成。比如,从设备文件读取鼠标坐标的信息并对其进行处理,不一定都在Mouse_getxy中完成,可以在Wait_event或update_mouse中完成,在mouse_getxy中只是将最终的显示屏幕坐标赋给mouse_getxy函数的两个参数。

b) IAL引擎的作用是正确分析从设备文件读得的数据,获得鼠标(触摸屏、触摸板)坐标和状态及键盘按键情况(哪个键按下?该键是不是释放了?).这也是我们开发IAL引擎的指南。

c) IAL引擎在MiniGUI的使用(位置):

在\src\sever\server.c L396 函数IdleHandler4Server()中用了输入引擎中的IAL_WaitEvent检查是否有底层输入事件发生,

当有事件发生时检查是鼠标(触摸屏、触摸板)事件发生还是键盘事件发生,并分别用parseEvent(msg_que,event)函数(\src\sever\server.c)处理这些事件

ParseEvent (msg_queue, IAL_MOUSEEVENT)

ParseEvent (msg_queue, IAL_KEYEVENT)

ParseEvent (msg_queue, 0)

parseEvent(msg_que,event)函数中首先调用了Getlwevent(event,&lwe) (lwe是在parseEvent中定义的),Getlwevent(event,&lwe)中分析event:

当event是IAL_KEYEVENT时:

调用了引擎IAL_UpdateKeyboard ()和IAL_GetKeyboardState ()

当event是IAL_MOUSEEVENT时:

调用了引擎IAL_UpdateMouse ()和函数RefreshCursor(&me->x, &me->y, &button)(该函数中调用了IAL_GetMouseXY (x, y)和IAL_GetMouseButton )

Getlwevent(event,&lwe)最终得到了鼠标键盘的坐标状态等具体信息,这些信息由lwe变量带回。

变量Lwe数据结构:

typedef struct _LWEVENT

{

int type;

int count;

DWORD status;

LWEVENTDATA data;

}LWEVENT;

typedef union _LWEVENTDATA {

MOUSEEVENT me;

KEYEVENT ke;

}LWEVENTDATA;

parseEvent(msg_que,event)中利用了获得的lwe,将其转化为消息,放到消息队列中。如对键盘:

Msg.message=Msg-KEYDOWN

Msg.wparam=ke->scancode

Msg.iparam=ke->status

Getlwevent(event,&lwe)收集底层输入事件lwe(利用了IAL引擎)

parseEvent(msg_que,event)将收集到的这些事件转化为上层能理解的消息。



d) 有关键盘的IAL引擎就是给state数组赋值。

MiniGUI在include/common.h中列举了每种按键在state数组中的位置(如#define SCANCODE_Z 44可以知道state数组中第44个元素表示Z键的状态),我们必须分析从键盘配置文件读取的字符字节的信息,根据这些信息给state数组中相应元素赋值,这样MiniGUI才能根据state数组正确处理这些事件(即MiniGUI是通过state数组中的元素来正确识别哪个键按下的。必须严格根据state数组的规定)。



8. 编写输入引擎可参考ipaq.c,它是针对了比较普通的触摸屏的写的。

9. 写出触摸屏接口代码,注意:鼠标获取的数据一般是相对坐标,而典型的触摸屏则是绝对坐标(差劲的驱动是直接返回A/D转换得来的原始数据)。

PC键盘驱动程序源码分析

Software

一. 编写目的:

描述uclinux内核中pc机键盘驱动的体系结构和工作原理,用于指导针对具体的嵌入式键盘的驱动程序的编写。

二. 参考资料:

1.《Linux内核源代码情景分析(下册)》第8.7和8.8章节,page330~412

2.内核源代码文件:

../linux-2.4.x/drivers/char/keyboard.c

../linux-2.4.x/include/asm-i386/keyboard.h

../linux-2.4.x/drivers/char/pc_keyb.c

../linux-2.4.x/drivers/input/*.*

../linux-2.0.x/drivers/char/keyboard.c

3.网络文章:《书写基于内核的linux键盘记录器》

三. pc键盘驱动工作流程:

1. 键盘初始化

该工作主要是由tty初始化函数tty_init()调用键盘驱动程序模块的初始化函数kbd_init()实现。Kbd_init()函数主要调用initialize_kbd()函数完成工作。

主要完成工作为,键盘的自检,检测,启动,寄存器设置等;并且向系统注册键盘中断服务函数。

2. 键盘中断响应过程

当用户按键或者释放键时,键盘向系统产生中断信号,系统自动进入键盘中断服务函数处理,该部分工作主要由键盘中断服务函数keyboard_interrupt()完成。

主要完成工作为:从键盘状态寄存器读取键盘状态,从键盘缓冲区读取数据,根据读取的状态和数据,进行键码转换等工作,将结果存入一个tty的“flip_buffer”的数据缓冲区。在中断服务函数最后,进行键盘后端处理函数(其实是控制台终端的tasklet,即console_tasklet())调度。

3. 键盘后端处理(不属于键盘驱动程序处理范畴)

在键盘中断服务函数结束之前,会将键盘后端处理函数(其实是控制台终端的tasklet,即console_tasklet())挂入后端处理队列,系统在调度的时候最终执行该函数。

在该函数中将完成一些键盘的相关工作,例如将flip_buffer中的键盘数据加以处理,将结果存入tty的read_buffer数据缓冲区。

4. 键盘数据最终结果传递到用户进程(不属于键盘驱动程序处理范畴)

在tty的file_operations数据结构的read函数指针指向read_charn()函数,该函数从read_buffer数据缓冲区获取数据,返回给用户进程。

如此一次键盘回话完成。

5.

四. 源文件具体分析:

有一点必须注意,在linux-2.0.x的内核中,键盘驱动主要工作都是在

../linux-2.0.x/drivers/char/keyboard.c

文件中完成,而没有别的文件,不象linux-2.4.x内核除了文件:

../linux-2.4.x/drivers/char/keyboard.c

还有以下这些文件:

../linux-2.4.x/drivers/char/pc_keyb.c

../linux-2.4.x/include/asm-i386/keyboard.h

我们这里主要分析的时候linux-2.4.x内核的键盘驱动程序。

另外还有一些相关代码:

../linux-2.4.x/drivers/char/vt.c

../linux-2.4.x/drivers/char/tty_io.c

../linux-2.4.x/drivers/char/tty_ioctl.c

../linux-2.4.x/drivers/input/*.*

../linux-2.4.x/include/linux/kbd_kern.h

../linux-2.4.x/drivers/char/console.c

1.../linux-2.4.x/drivers/char/pc_keyb.c

(1) void __init pckbd_init_hw(void)

键盘初始化函数,该函数由Kbd_init()调用(Kbd_init()调用的是kbd_init_hw(),但是在i386中,kbd_init_hw被#define为pckbd_init_hw)。

主要完成工作:

a. 根据kbd_controller_present判断键盘控制器是否存在

b. 调用kbd_request_region()分配资源

c. 调用kbd_clear_input()清除键盘控制器缓冲区数据

d. 键盘如果未复位初始化,则调用函数initialize_kbd()进行初始化

e. 设置kbd_rate函数指针为pckbd_rate()函数

f. 调用kbd_request_irq(),将键盘中断服务函数keyboard_interrupt()注册到系统。

(2) static char * __init initialize_kbd(void)

键盘初始化函数,该函数由pckbd_init_hw()调用。

主要完成工作:

a. 调用kbd_write_command_w(KBD_CCMD_SELF_TEST),进行键盘自检

b. 调用kbd_write_command_w(KBD_CCMD_KBD_TEST),进行键盘检测

c. 调用kbd_write_command_w(KBD_CCMD_KBD_ENABLE),使能键盘

d. 调用kbd_write_output_w(KBD_CMD_RESET)复位键盘,并且调用函数kbd_wait_for_input()接受复位状态字节并且判断复位是否成功,如果复位成功,继续,否则函数返回

e. 调用kbd_write_output_w(KBD_CMD_DISABLE),在设置键盘工作模式之前,停止键盘工作。调用kbd_wait_for_input()接受停止键盘状态,如果停止成功,继续,否则函数返回

f. 调用kbd_write_command_w(KBD_CCMD_WRITE_MODE)和kbd_write_output_w(…)设置键盘工作模式。

g. 对于powerpc键盘的一些模式设置

h. 调用kbd_write_output_w_and_wait(KBD_CMD_ENABLE),在完成键盘工作模式设置之后,使能键盘工作

i. 最后调用kbd_write_output_w_and_wait(KBD_CMD_SET_RATE),set the typematic rate to maximum

(3) static int kbd_write_output_w_and_wait(int data)

发送数据到键盘数据端口函数

主要完成工作:

a. 调用kbd_write_output_w(data)函数,往键盘数据端口发送数据

b. 调用kbd_wait_for_input()等待键盘的回应数据。

(4) static int kbd_write_command_w_and_wait(int data)

发送命令到键盘数据端口函数

主要完成工作:

a. 调用kbd_write_command_w(data)函数,往键盘命令(控制)端口发送命令

b. 调用kbd_wait_for_input()等待键盘的回应数据

(5) static void kbd_write_output_w(int data)

发送数据到键盘的数据端口函数,主要由kbd_write_output (data)完成工作,kbd_write_output (data)函数和体系结构非常密切,一般由汇编代码实现

(6) static void kbd_write_command_w(int data)

发送命令到键盘的命令(控制)端口函数,主要由kbd_write_command(data)完成工作,kbd_write_command(data)函数和体系结构非常密切,一般由汇编代码实现。

(7) static int __init kbd_wait_for_input(void)

延时等待键盘返回数据函数,即循环等待的时候kbd_read_data()调用函数从键盘的读取数据。

(8) static void __init kbd_clear_input(void)

发送清除键盘数据,即调用函数kbd_read_data()不停地从键盘读取数据,知道没有数据为止。

(9) static int __init kbd_read_data(void)

从键盘读取数据

主要完成工作:

a. 调用kbd_read_status()函数从键盘状态寄存器读取键盘地状态,该函数和体系结构关系密切,一般由汇编代码实现

b. 判断键盘缓冲区是否有数据,如果有则调用函数kbd_read_input()从键盘数据寄存器读取数据,该函数和体系结构关系密切,一般由汇编代码实现

c. 判断读取地数据是否有效

(10) line657~679不懂

(11) static int pckbd_rate(struct kbd_repeat *rep)

在pckbd_init_hw()函数中被赋值给函数指针kbd_rate,被../linux-2.4.x/drivers/char/vt.c 文件vt_ioctl()调用

(12) static int write_kbd_rate(unsigned char r)

被函数pckbd_rate(),是一个内部函数

(13) static unsigned char parse_kbd_rate(struct kbd_repeat *r)

被函数pckbd_rate(),是一个内部函数

(14) void pckbd_leds(unsigned char leds)

在文件../linux-2.4.x/include/asm-i386/keyboard.h被定义成宏:kbd_leds()。被键盘中断后端处理函数kbd_bh()函数调用。

主要功能:

调用函数send_data()设置键盘的led灯,如果失败设置键盘不存在。

(15) static int send_data(unsigned char data)

发送字节data到键盘,并且等待键盘的回应。

(16) static void keyboard_interrupt(int irq, void *dev_id, struct pt_regs *regs)

键盘中断服务函数(最关键),主要是调用函数handle_kbd_event()完成工作。在整个函数执行过程必须关闭中断。

注意:ps鼠标和键盘共用键盘中断服务函数

(17) static unsigned char handle_kbd_event(void)

中断事件处理函数,该函数由keyboard_interrupt()调用

主要完成以下工作:

a. 调用kbd_read_status()读取键盘状态端口

b. 循环执行以下操作,知道根据状态寄存器判断没有数据,或者已经读取了1000个数据:

调用kbd_read_input()读取数据

根据状态寄存器的值,判断是ps鼠标中断,则调用鼠标中断事件处理函数handle_mouse_event(),如果是键盘中断,则调用handle_keyboard_event(unsigned char scancode)。

重新读取状态寄存器

c.

(18) static inline void handle_keyboard_event(unsigned char scancode)

该函数为键盘中断事件处理函数,由handle_kbd_event()函数调用,主要工作由handle_scancode()函数实现。

主要完成工作:

a. 调用do_acknowledge(scancode)发送通知数据收到信息给键盘

b. 调用handle_scancode()函数处理scancode。Handle_scancode()函数在文件../linux-2.4.x/drivers/char/keyboard.c中实现

c. 调用函数tasklet_schedule(&keyboard_tasklet),将剩余工作放到bh,即将键盘后端函数keyboard_tasklet挂入tasklet,系统自动会调度运行该函数。

(19) static inline void handle_mouse_event(unsigned char scancode)

由于ps鼠标不在该范畴内,在此不加以分析。

(20) static int do_acknowledge(unsigned char scancode)

该函数处理当从键盘接收到一个数据时,往键盘发送ACK信息。

主要完成工作:

a. 根据reply_expected判断是否需要往键盘发送ACK信息

b. 如果需要,根据具体的scancode进行处理

(21) int pckbd_pm_resume(struct pm_dev *dev, pm_request_t rqst, void *data)

该函数是关于PS鼠标的函数,在此不分析

(22) char pckbd_unexpected_up(unsigned char keycode)

进行一些不预期的按键释放等处理,相应清除一些标志

(23) int pckbd_translate(unsigned char scancode, unsigned char *keycode,char raw_mode)

将scancode转换为keycode,该函数在../linux-2.4.x/include/asm-i386/keyboard.h 文件中被定义成kbd_translate,在handle_scancode()函数中被调用。具体实现参考源代码

(24) int pckbd_getkeycode(unsigned int scancode)

根据scancode和keycode数组e0_keys[128]或者high_keys[]数组获取keycode,该函数在../linux-2.4.x/include/asm-i386/keyboard.h中被定义为宏kbd_getkeycode,在文件../linux-2.4.x/drivers/char/keyboard.c中被getkeycode()函数调用。

主要功能:

根据scancode获取pc键盘的功能键码

(25) int pckbd_setkeycode(unsigned int scancode, unsigned int keycode)

根据scancode将eo_keys[128]或者high_keys[]对应项的值设置为keycode,该函数在../linux-2.4.x/include/asm-i386/keyboard.h中北定义为宏kbd_getsetkeycode,在文件../linux-2.4.x/drivers/char/keyboard.c中被getsetkeycode()函数调用。

主要功能:

根据设置pc键盘的功能键码为scancode

(26) static void kb_wait(void)

循环等待,在等待的时候调用函数handle_kbd_event()监视键盘状态,超时或者状态满足则退出循环等待。

(27)

2.../linux-2.4.x/drivers/char/keyboard.c

(1) void handle_scancode(unsigned char scancode, int down)

该函数在pc键盘驱动中是非常关键的一个函数,主要是将scancode转换为tty所能接受的码制,例如ascii码,unicode等,具体根据需求而定。并且将转换结果存入flip_buffer;在控制台的后端处理函数将从flip_buffer读取数据到read_buffer;而tty驱动程序的读取函数(read:read_chan())从read_buffer读取数据,从而完成键盘的一个回话。

具体实现请参考《linux内核源代码情景分析(下册)》的page375开始的内容

(2) int __init kbd_init(void)

该函数在pc键盘驱动中也是非常关键的一个函数,键盘驱动,键盘的初始化都由该函数实现,主要功能通过调用函数kbd_init_hw()实现,而kbd_init_hw()是一个宏,具体实现函数为pckbd_init_hw()。

该函数被tty驱动的初始化函数即../linux-2.4.x/drivers/char/tty_io.c文件中的tty_init()函数调用。

具体实现:

a. 初始化kbd_table数组,即每个控制台的键盘状态。

b. 获取ttytab指针(也可以说是一个数组,数组成员为每个控制台对应的tty)。

c. 调用kbd_init_hw()函数,实现初始化

d. 使能键盘中断服务后端函数运行,并且挂接keyboard_tasklet()(其实就是kbd_bh()函数)为键盘中断服务后端执行函数

e. 调用函数pm_register()将键盘注册到电源管理设备列表,最后一个参数为回调函数,在此好像是NULL

(3) static void kbd_bh(unsigned long dummy)

键盘中断服务程序的后端服务函数,主要完成console changing, led setting and copy_to_cooked等比较花时间的工作。

主要功能:

完成键盘的numlock,capslock和scrolllock的led指示灯设置。

(4) int getkeycode(unsigned int scancode)

该函数在../linux-2.4.x/drivers/char/vt.c文件line255处被do_kbkeycode_ioctl()调用。

(5) int setkeycode(unsigned int scancode, unsigned int keycode)

该函数在../linux-2.4.x/drivers/char/vt.c文件line262处被do_kbkeycode_ioctl()调用。

(6) static inline unsigned char getleds(void)

该函数被键盘中断后端处理函数kbd_bh()调用

(7) void register_leds(int console, unsigned int led,unsigned int *addr, unsigned int mask)

(8) void setledstate(struct kbd_struct *kbd, unsigned int led)

该函数在../linux-2.4.x/drivers/char/vt.c文件Line690的vt_ioctl()函数中被调用。

具体功能:

设置键盘NumLock, CapsLock,或ScrollLock的led灯的状态。

(9) unsigned char getledstate(void)

该函数在../linux-2.4.x/drivers/char/vt.c文件Line683的vt_ioctl()函数中被调用。

具体功能:

读取键盘NumLock, CapsLock,或ScrollLock的led灯的状态。

以下函数都是handle_scancode()函数处理scancode的时候调用的内部函数:

(10) void put_queue(int ch)

(11) static void puts_queue(char *cp)

上面这两个函数(8),(9)比较关键,因为就是由这两个函数将转换结果存入flip_buffer,并且将控制台的后端服务函数,即bh函数挂入tasklet。

(12) void compute_shiftstate(void)

该函数被许多地方调用,例如:

../linux-2.4.x/drivers/char/console.c

../linux-2.4.x/drivers/char/vt.c

主要功能:

设置shift_state全局变量,该变量是应该是和pc键盘上的“shift”键的状态相关联。

(13) unsigned char handle_diacr(unsigned char ch)

(14) static void SAK(void)

(15) static void spawn_console(void)

(16) static void compose(void)

(17) static void boot_it(void)

(18) static void scroll_back(void)

(19) static void scroll_forw(void)

(20) static void send_intr(void)

(21) static void incr_console(void)

(22) static void decr_console(void)

(23) static void lastcons(void)

(24) static void bare_num(void)

(25) static void num(void)

(26) static void hold(void)

(27) static void show_ptregs(void)

(28) static void caps_on(void)

(29) static void caps_toggle(void)

(30) static void enter(void)

(31) static void applkey(int key, char mode)

(32) void to_utf8(ushort c)

(33) static void do_slock(unsigned char value, char up_flag)

(34) static void do_lock(unsigned char value, char up_flag)

(35) static void do_ascii(unsigned char value, char up_flag)

(36) static void do_meta(unsigned char value, char up_flag)

(37) static void do_dead2(unsigned char value, char up_flag)

(38) static void do_shift(unsigned char value, char up_flag)

(39) static void do_cur(unsigned char value, char up_flag)

(40) static void do_pad(unsigned char value, char up_flag)

(41) static void do_fn(unsigned char value, char up_flag)

(42) static void do_cons(unsigned char value, char up_flag)

(43) static void do_dead(unsigned char value, char up_flag)

(44) static void do_self(unsigned char value, char up_flag)

(45) static void do_spec(unsigned char value, char up_flag)

(46) static void do_ignore(unsigned char value, char up_flag)

五. 键盘驱动和系统上层的接口,以下函数就是在根据具体硬件的时候特别关注的函数:

键盘驱动和系统上层的接口主要就是和tty驱动的接口,用户和键盘打交道一般都是通过tty的接口函数进行。

1. kbd_init()-键盘初始化函数:tty驱动程序的初始化函数tty_init()调用键盘初始化函数kbd_init()函数

2. Put_queue()或者puts_queue()函数:数据缓冲区和tty的接口,键盘驱动(具体地说是键盘中断服务函数)将从键盘读取地数据存入flip_buffer数据缓冲区,而用户调用tty驱动的read函数(即read_chan())则从flip_buffer读取数据。

3. ../linux-2.4.x/drivers/char/tty_ioctl.c 文件n_tty_ioctl()函数调用的地方。

4. ../linux-2.4.x/drivers/char/vt.c 文件vt_ioctl()调用的地方。

(1) void setledstate(struct kbd_struct *kbd, unsigned int led)

(2) unsigned char getledstate(void)

(3) int getkeycode(unsigned int scancode)

(4) int setkeycode(unsigned int scancode, unsigned int keycode)

(5) void compute_shiftstate(void)

(6) static int pckbd_rate(struct kbd_repeat *rep)

5. ../linux-2.4.x/drivers/chr/console.c文件redraw_screen()调用的地方

(1) void compute_shiftstate(void)

(2)

6. 另外注意的函数:

(1) static void kbd_bh(unsigned long dummy)

(2) void pckbd_leds(unsigned char leds)

7.

六.

一个简单的IAL分析(红外遥控)

Software

一、程序说明    1.下面程序是基于一个红外的设备文件,从该设备中能接收到红外遥控的硬件编码。    2.两个文件需要覆盖掉libmingiui*/src/ial/中的两个文件编译时加上 --enable-video-qvfb    3.为了简单,就直接在qvfb引擎上进行修改二、程序源码及代码说明/*qvfb.h*/#ifndef _IAL_qvfb_h#define _IAL_qvfb_h#define kb_dev   "/dev/ir"//一个现成的红外设备,当按钮按下后可以从这里读到一个整型#define TIME_KEYUP  30//设定一个按钮自动弹起的时钟30msstruct QVFbKeyData{    unsigned int unicode;    //unsigned int modifiers;    //BYTE press;    //BYTE repeat;};//读到红外数据的结构,实际是一个无符号整型#ifdef __cplusplusextern "C" {#endif  /* __cplusplus */BOOL InitQVFBInput (INPUT* input, const char* mdev, const char* mtype);void TermQVFBInput (void);#ifdef __cplusplus}#endif  /* __cplusplus */#endif /* _IAL_qvfb_h */***文件结束************************************/*qvfb.c*/#include #include #include #include #include #include #include "common.h"#ifdef _QVFB_IAL#include #include #include #include #include "minigui.h"#include "misc.h"#include "ial.h"#include "qvfb.h"extern unsigned int __mg_timer_counter;// minigui自带的一个计时器static int mouse_fd = -1;static int kbd_fd = -1;//static POINT mouse_pt;//static int mouse_buttons;static struct QVFbKeyData kbd_data;static unsigned char kbd_state [NR_KEYS];static unsigned char last = 0;//保存上一次取道的扫描码   static int time_pre = -1 ;//下面是一个硬件码到扫描码的转化表static int keycode_scancode[0x21] ={ SCANCODE_CURSORBLOCKUP,   /*0x00 UP0   UP*/ SCANCODE_CURSORBLOCKDOWN,   /*0x01 Down   DOWN*/ SCANCODE_CURSORBLOCKRIGHT,   /*0x02 Right   RIGHT*/ SCANCODE_CURSORBLOCKLEFT,   /*0x03 Left    LEFT*/ 0x003C,   /*0x04 Audio  F2*/ SCANCODE_LEFTALT,   /*0x05 MENU*/ 0x0040,   /*0x06 Radio  电/广F6*/ SCANCODE_ESCAPE,   /*0x07 Exit    ESC*/ 0x003D,   /*0x08 Pause  F3*/ 0x003B,   /*0x09 FAV   F1*/ 0x0044,   /*0x0A OWER  F10*/ 0x003F,   /*0x0B TV  缩放F5*/ 0x0057,   /*0x0C MUTE  F11*/ 0x0000,   /*0x0D 没有该按钮*/ 0x0042,   /*0x0E TV/Sat 搜台F8*/ 0x003E,   /*0x0F TXT   F4*/ SCANCODE_0,   /*0x10 0   0*/ SCANCODE_1,   /*0x11 1   1*/ SCANCODE_2,   /*0x12 2   2*/ SCANCODE_3,   /*0x13 3   3*/ SCANCODE_4,   /*0x14 4   4*/ SCANCODE_5,   /*0x15 5   5*/ SCANCODE_6,   /*0x16 6   6*/ SCANCODE_7,   /*0x17 7   7*/ SCANCODE_8,   /*0x18 8   8*/ SCANCODE_9,   /*0x19 9   9*/ SCANCODE_CAPSLOCK,   /*0x1A quality  CAPLOCK*/ SCANCODE_BACKSPACE,   /*0x1B recall  BACKSPACE*/ SCANCODE_PAGEUP,   /*0x1C Sat   PAGEUP*/ SCANCODE_PAGEDOWN,   /*0x1D Skew  PAGEDOWN*/ 0x0043,   /*0x1E Guid  F9*/ SCANCODE_ENTER,   /*0x1F OK   ENTER*/ 0x0041   /*0x40 Info  F7*/};static unsigned char keycode_to_scancode (unsigned int keycode){ keycode >>= 16; keycode &= 0x0FF;  fprintf (stderr, "IAL:keycode = %d!\n",keycode); if(keycode >= 0x20) {  keycode = 0x20;  } return keycode_scancode[keycode];}/************************  Low Level Input Operations **********************//* * Mouse operations -- Event*鼠标函数空,忽略 */static int mouse_update (void){    return 1;}static void mouse_getxy (int *x, int* y){ ;}static int mouse_getbutton (void){    return 0;}////////////////////////////////////////////static int keyboard_update (void){    struct QVFbKeyData l_kbd_data;    int ret;    if(!(last & 0x80)) {     ret = read (kbd_fd, &l_kbd_data, sizeof (struct QVFbKeyData));  ////////////////////判断是否有数据     if (ret == sizeof (struct QVFbKeyData)) {         kbd_data = l_kbd_data;     }     else     {         return 0;     }  /////////////////////////////////        //按钮未弹起前不接收其他按钮     last = keycode_to_scancode (kbd_data.unicode);     kbd_state[last]=1;   //新按钮按下     time_pre = __mg_timer_counter;//开始按钮弹起计时     fprintf (stderr, "IAL:Key Down!\n");    }    else    {     kbd_state[last & 0x7f]=0;     time_pre = -1; last = 0;     fprintf (stderr, "IAL:Key Up!\n");    }    return NR_KEYS;}static const char* keyboard_getstate (void){    return kbd_state;}/* NOTE by weiym: Do not ignore the fd_set in, out, and except */#ifdef _LITE_VERSIONstatic int wait_event (int which, int maxfd, fd_set *in, fd_set *out, fd_set *except,                struct timeval *timeout)#elsestatic int wait_event (int which, fd_set *in, fd_set *out, fd_set *except,                struct timeval *timeout)#endif{ int interval;    fd_set rfds;    int    retvalue = 0;    int    fd, e; //////////////////////////////// if(time_pre > 0) {  interval= __mg_timer_counter - time_pre;  if (interval > TIME_KEYUP)  {   //kbd_state[last] = 0;   last |= 0x80; //发送按钮弹起数据   retvalue |= IAL_KEYEVENT ;   return retvalue;  } }   /////////////////////////////////    if (!in) {        in = &rfds;        FD_ZERO (in);    }    if (which & IAL_MOUSEEVENT && mouse_fd >= 0) {        fd = mouse_fd;        FD_SET (fd, in);#ifdef _LITE_VERSION        if (fd > maxfd) maxfd = fd;#endif    }    if (which & IAL_KEYEVENT){        fd = kbd_fd;        FD_SET (kbd_fd, in);#ifdef _LITE_VERSION        if (fd > maxfd) maxfd = fd;#endif    }    /* FIXME: pass the real set size */#ifdef _LITE_VERSION    e = select (maxfd + 1, in, out, except, timeout) ;#else    e = select (FD_SETSIZE, in, out, except, timeout) ;#endif    if (e > 0 && time_pre < 0) { //按钮按下后就不产生事件,直到按钮弹起        fd = mouse_fd;        /* If data is present on the mouse fd, service it: */        if (fd >= 0 && FD_ISSET (fd, in)) {            FD_CLR (fd, in);            retvalue |= IAL_MOUSEEVENT;        }        fd = kbd_fd;        /* If data is present on the keyboard fd, service it: */        if (fd >= 0 && FD_ISSET (fd, in)) {            FD_CLR (fd, in);            retvalue |= IAL_KEYEVENT;        }    } else if (e < 0 || time_pre > 0) {        return -1;    }    return retvalue;}BOOL InitQVFBInput (INPUT* input, const char* mdev, const char* mtype){ kbd_fd = open (kb_dev, O_RDONLY);     if (kbd_fd < 0) {        fprintf (stderr, "IAL:Can not open /dev/ir !\n");    }/////////////////////////////////////////////////////    input->update_mouse = mouse_update;    input->get_mouse_xy = mouse_getxy;    input->set_mouse_xy = NULL;    input->get_mouse_button = mouse_getbutton;    input->set_mouse_range = NULL;    input->suspend_mouse= NULL;    input->resume_mouse = NULL;    input->update_keyboard = keyboard_update;    input->get_keyboard_state = keyboard_getstate;    input->suspend_keyboard = NULL;    input->resume_keyboard = NULL;    input->set_leds = NULL;    input->wait_event = wait_event;    return TRUE;}void TermQVFBInput (void){    if (kbd_fd >= 0)        close (kbd_fd);}#endif /* _QVFB_IAL */***文件结束************************************

轻松控制uClinux嵌入式开发过程

Software

  uClinux是目前比较普及的嵌入式Linux版本之一,它的功能很多,并且随着低成本、可运行uClinux的32位CPU的激增,以及uClinux首次成为Linux 2.6内核的一部分,uClinux将更加流行(如图1)。下面讨论一下开发者使用uClinux时如何控制开发过程,以及将会遇到的与普通Linux的不同之处。
  

  
图1 uClinux运行在Palm上

  
  应用无内存管理
  
  uClinux与普通Linux系统的主要区别就是它没有内存管理。在普通Linux下,通过使用虚拟内存(VM)来实现内存管理。虚拟内存一般是通过内存管理单元(Memory Management Unit,简称MMU)来实现,而在uClinux的世界里,经常可以看到“NOMMU”这个词。
  
  在有虚拟内存的情况下,所有的进程都在相同的地址空间运行,由虚拟内存系统处理虚拟内存到物理内存的映射。因此,即使进程看到的虚拟内存是连续的,它所占的物理内存也可能是分散的,有的甚至被交换到了硬盘。因为物理内存能映射到进程地址空间的任何位置,所以这种环境下能够向正在运行的进程添加内存。
  
  在没有虚拟内存的情况下,每个进程必须被分配到固定的内存位置。由于一个进程的上、下(内存位置)都可能有别的进程在运行,所以它通常不能动态扩展内存。这就是说,在uClinux下运行的进程不能在运行过程中动态增加可用内存,这与传统Linux下的情况有所不同。
  
  对于uClinux开发者来说,分配内存是一个棘手的问题,并且由于没有任何形式的内存保护,任何应用程序或内核都可能破坏系统。更为糟糕的是,无意识的误操作不会引人注意,造成要跟踪随机的、进程间的破坏非常困难。但是这些缺陷对于uClinux来说几乎不算问题,这是因为使用uClinux的系统一般没有硬盘驱动器和足够的内存,完全没有必要做复杂的管理和交换。
  
  做足内存映射
  
  对于内核开发者,uClinux与普通Linux区别很小。惟一真正会遇到的问题是uClinux内核开发者不能利用MMU提供的分页支持,比如,依赖虚拟内存的 tmpfs文件系统在uClinux下就不起作用。类似的,普通Linux下的标准可执行文件格式uClinux都不支持,因为它们都要利用虚拟内存的特性。uClinux需要一种新的格式——Flat,它是一种压缩的可执行文件格式,只保存可执行的代码和数据,以及将可执行程序装载到内存时所需要的重定位信息。
  
  理解uClinux内核中内存映射的实现方式也是很有必要的,因为有些方式在uClinux系统上行不通,理解内存映射的实现后可以避免使用这些方式。uClinux要求内存映射能够直接在文件系统中指到文件,从而保证它是顺序的和连续的,否则就必须事先为文件分配好内存,并把数据拷贝到分配给它的内存块上。
  
  因此,uClinux下有效内存映射的用法要素非常明确:首先,当前惟一能够保证文件连续存储的文件系统是ROM文件系统(Romfs),所以必须使用Romfs来避免传统内存分配;其次,只有只读的内存映射能够被共享,也就是说,为了避免传统内存分配,映射必须是只读的。由于这些原因,uClinux下的开发者不能利用“Copy-on-Write”特性。
  
  要将设备驱动程序移植到uClinux环境,需要做一些修改,这并不是因为内核上的区别,而是由于与硬件细节相关部分有所不同造成的。比如,普通Linux下, SMC网络驱动程序可以支持ISA SMC卡。该驱动程序是16位的,并且一般都分配到0x3ff以下的I/O地址空间。
  
  但是用来支持SMC卡的非ISA嵌入式版本,驱动程序要求运行在8位、16位或32位模式下都是可能的,并且在满32位的I/O地址中,中断号一般要高于ISA的最大值16。所以,与硬件细节相关的部分可能还是要做一些移植工作。
  
  恰当的内存分配
  
  uClinux除了提供跟普通Linux一样的内存分配器之外,还提供另一个可选的。普通Linux中缺省的内存分配器是使用“2的幂”的分配方法,这样可以快速找到符合要求的内存区域。不幸的是,在uClinux下这种方法可能会带来令人痛苦的结果。
  
  为了理解这一问题带来的结果,尤其是大的内存分配,我们举例说明。试想一个应用程序要求33KB的内存空间进行装载。如果使用“2的幂”的分配方法,就必须分配64KB(2的6次方)内存空间,多余的31KB内存空间不能被利用上。在uClinux中,这种浪费是不能接受的。为了解决这个问题,专门为 uClinux内核设计了可选的内存分配器。不同的内核版本,这个可选的内存分配器不同,一般是page_alloc2和kmalloc2。
  
  page_alloc2能解决缺省的分配方法造成的浪费问题。虽然它也是使用“2的幂”的分配方法,但它是按页(每页4096字节,即4KB)分配的,分配的内存大小如果已经满足了要求,则只是将当前的一页分配出去,其它的就不再分配。在前面的例子中,如果使用这种方法,就只是分配36KB (≥33KB,且为整页)即可,这样就能节省28KB的空间。
  
  page_alloc2还采取了一些避免内存碎片的方法。它将所有的两页(8KB)或更少的内存需求从空闲内存开始部分向上分配,所有大的内存需求从剩余内存的末尾部分开始向下分配。这样防止了网络缓存等的临时分配,避免了内存碎片的出现。
  
  一旦开发者理解了内核内存分配的区别,应用程序中就会出现变化。
  
  1.没有动态栈的问题
  
  在使用虚拟内存的Linux上,当一个应用程序试图冲销栈顶单元时,会被标记异常,同时系统会映射新的内存到栈顶以便让栈增长。在uClinux下,由于必须在编译阶段给栈分配好内存,所以不会有这样的增长。当出现莫名其妙的崩溃或者新移植的应用程序出现怪异行为时,开发者首先应该考虑到的是给栈分配的内存大小问题。缺省情况下,uClinux为栈分配4KB的内存空间,开发者可以用下面提到的方法之一来增加栈的空间。
  
  ◆ 应用程序build之前
  
  应用程序build之前,可以在Makefile文件中增加以下两行代码:
  
  FLTFLAGS = -s
  export FLTFLAGS
  
  ◆ 应用程序build之后
  
  应用程序build之后,可以运行以下命令:
  
  flthdr -s stacksize executable
  
  其中,stacksize 就是为栈增加的内存空间(十进制)。
  
  2.没有动态堆的问题
  
  堆是C语言中malloc及相关函数分配内存的区域。在有虚拟内存的Linux上,应用程序可能通过动态堆在运行过程中改变进程的大小。这个功能是通过在底层使用sbrk()和brk()系统调用来实现的。sbrk()是在进程的末尾增加内存空间,所以调用sbrk()能够使应用程序获得额外的内存。
  
  brk()可以把任意位置设置为进程空间的末尾,因此,可以通过调用brk()减少或增加内存空间的占用。由于uClinux不能实现brk()和sbrk(),它采用了一个全局的内存池,就是内核的空闲内存池。使用全局内存池的方法有一些优点。

  首先,此方法只会给进程分配使用时真正需要的内存。其次,内存用完后就会被归还给全局内存池,而且可以利用已经存在的内核中的分配器来分配内存,这样可以减少应用程序的代码量。但这个方法是有缺陷的,比如,一个失控的进程可以用完系统全部的可用内存。
  
  新手普遍会遇到丢失内存的问题。系统会显示大量的可用内存,但是应用程序却不能得到。这正是由于内存碎片的存在,uClinux几乎不可能完全利用内存,现有的解决方法中都存在这个问题。这个问题可用一个例子很好地说明。
  
  假设一个系统有500KB的空闲内存,为了装载一个应用程序需要分配100KB的空间。大家可能觉得这个需要肯定能得到满足,然而,应该知道,必须有 100KB连续的内存空间才能满足这个需要。如果有500KB的空闲空间,但是最大的连续内存块的大小只有80KB,这样是没有办法分配给这个应用程序的。造成这种情况有很多原因。上面讲到的page_alloc2内核分配器有一个配置选项可以用来识别这个问题,在内核源代码page_alloc2.c 文件中可以获得更多的信息。
  
  经常有人会问为什么不能进行内存的碎片整理,以便实现刚才的例子中的要求?原因是uClinux没有虚拟内存,所以不能移动程序正在使用的内存。在使用虚拟内存的情况下,只要重新定位就能实现内存的移动,从而实现内存碎片的整理。
  
  在没有虚拟内存的情况下,由于程序经常会引用已经分配给它的内存区域,这样,如果移动程序的内存,程序就会崩溃。在uClinux下,现在还没有解决这个问题的办法。开发者需要自己注意这个问题,如果有可能的话,尽量使用小的内存块。
  
  掌控进程和应用程序
  
  1.进程
  
  有虚拟内存的Linux和uClinux的另一个区别在于后者没有fork()系统调用。这就要求开发者在移植时对使用了fork()的应用程序做一些工作。uClinux下惟一的选择是使用vfork()。尽管vfork()与fork()有很多共同点,但是它们之间的区别影响很大。
  
  对于不熟悉fork()和vfork()的人来说,这两个系统调用都是允许将一个进程分裂成一个父进程和一个子进程。当一个进程调用fork()时,子进程是父进程的一个完全拷贝,但是它不共享父进程的任何东西,并且能够单独执行,就和父进程一样。vfork()调用就不同了,首先,父进程被挂起直到子进程调用exec(),或者子进程退出才能继续。
  
  由此可见,这个系统调用是用来启动一个新的应用程序。其次,子进程在vfork()返回后直接运行在父进程的栈空间,并使用父进程的内存和数据。这意味着子进程可能破坏父进程的数据结构或栈,造成失败。
  
  为了避免这些问题,需要确保一旦调用vfork(),子进程就不从当前的栈框架中返回,并且如果子进程改变了父进程的数据结构就不能调用exit函数。子进程还必须避免改变全局数据结构或全局变量中的任何信息,因为这些改变都有可能使父进程不能继续。
  
  通常,如果应用程序不是在fork()之后立即调用exec(),就有必要在fork()被替换成vfork()之前做仔细的检查。
  
  2.应用程序
  
  尽管uClinux的Flat可执行格式并不会直接影响应用程序和它们的执行,但是它允许许多普通Linux下的ELF可执行格式所不允许的选项。比如,Flat可执行格式带来两个衍生系统—完全重定位和位置无关代码(Position-Independent Code,简称PIC)的变体。完全重定位系统将对应用程序的代码和数据进行重定位,而PIC系统通常只需要对数据进行部分重定位。
  
  对嵌入式开发者最有用的特性就是运行时空间大小不变(Execute-In-Place,简称XIP)。这样应用程序可以直接从闪存(Flash)或 ROM中运行,因为只需要应用程序所需占用的内存即可。不是所有的uClinux平台都实现了XIP,因为它需要编译器的支持以及Flat可执行格式的 PIC形式。
  
  uClinux下的Romfs是惟一支持XIP的文件系统。要实现XIP,应用程序就必须被连续地装载到文件系统。 Flat格式还在它的头部定义了应用程序的栈大小。要增加分配给应用程序的栈,只需要简单地修改该部分,可以使用flthdr命令实现,格式如下:
  
  flthdr -s flat-executable
  
  Flat格式还允许整个可执行文件被压缩,以尽量缩小占用ROM的空间。它还有一个次要的作用就是使应用程序完全地装载到一个连续的RAM块中。既想节省ROM空间,又想使用XIP的时候,还可以选择Data-Segment-Only压缩形式。
  
  生成一个完全压缩的可执行文件:
  
  flthdr -z flat-executable
  
  只是生成压缩数据段:
  
  flthdr -d flat-executable
  
  特别小心共享库
  
  uClinux下的共享库各有不同。目前可用的解决方法需要修改编译器,并需要开发者特别小心。其实,当前的uClinux发行版本中提供了uC-libc和uClibc库,最好的方法是以这两个库为例子来创建自己的共享库。
  
  另外,uClinux下的共享库必须是Flat格式的可执行文件,并且要真正实现共享,必须实现XIP。如果不实现XIP,共享库就会为每个使用它的应用程序创建一份拷贝,这还不如使用静态链接应用程序。
  
  小结
  
  uClinux趋向于更深入的嵌入式系统,它需要更少的内存,并可直接在ROM上运行。如果初次在uClinux下开发的人遇到没有硬件驱动、有严格的资源限制,以及没有内存保护等一系列的情况,最好的入手方法就是使用uClinux仿真器(见图2)。
  

  
图2 uClinux仿真器Xcopilot

  
  强调以上这些问题有助于开发者提前做好准备,避免在uClinux下工作时常遇到陷阱和误解。

我手头项目的MiniGui编译移植[转]

Software

一、MiniGui的交叉编译

1. 编译命令

编译前需要准备内核,把内核的源代码放到/opt/uClinux/uClinux-dist目录下。

进入libminigui目录

[…]#CC=arm-elf-gcc AR=arm-elf-ar RANLIB=arm-elf-ranlib LDFLAGS="-Wl,-elf2flt" ./configure \

--prefix="/opt/usr/local" \

--host=arm-elf-linux \

--build=i386-linux \

--disable-shared \

--enable-lite=yes \

--enable-video-fbcon=yes \

--enable-nativegal=yes \

--enable-nativeial=yes \

--enable-newgal=no \

--enable-timerunitms=no \

--enable-nativegalqvfb=no \

--enable-fblin8=no \

--enable-fblin16=no \

--enable-fblin24=no \

--enable-fblin32=yes \

--enable-dummyial=yes \

--enable-qvfbial=no \

--enable-nativeps2=no \

--enable-nativeimps2=no \

--enable-nativems=no \

--enable-nativems3=no \

--enable-nativegpm=no \

--enable-textmode=no \

--enable-rbfsupport=yes \

--enable-rbfgb12=no \

--enable-vbfsupport=no \

--enable-fontsserif=no \

--enable-fontcourier=no \

--enable-fontsymbol=no \

--enable-fontvgas=no \

--enable-qpfsupport=no \

--enable-ttfsupport=no \

--enable-type1support=no \

--disable-ttfsupport \

--enable-latin2support=no \

--enable-latin3support=no \

--enable-latin4support=no \

--enable-latin9support=yes \

--enable-gbsupport=yes \

--enable-gbksupport=no \

--enable-big5support=no \

--enable-unicodesupport=no \

--enable-savebitmap=yes \

--enable-gifsupport=yes \

--enable-jpgsupport=no \

--enable-pngsupport=no \

--enable-imegb2312=no \

--enable-imegb2312py=no \

--enable-aboutdlg=yes \

--enable-savescreen=no \

--enable-tinyscreen=no \

--enable-extfullgif=no \

--enable-video-dummy=no \

--enable-extskin=no

[…]#make

[…]#make install

最后libminigui被安装在/opt/usr/local/目录下

上面配置的yes或no根据实际需要进行最优设置。

2. 资源安装

进入minigui-res目录

编辑config.linux文件大概在11行,把 TOPDIR=改为TOPDIR=/opt

[…]#make install

最后资源也被安装在/opt/usr/local/目录中

3. 输入引擎

引擎的入口代码:

src/ial.c 中有数组static INPUT inputs [],初始存放了所以输入引擎名称及其初始化函数和退出函数的入口,两个函数分别是InitNativeInput和TermNativeInput。

其他函数介绍:(主要介绍键盘相关的函数)

wait_event Minigui系统采用定时查询来获得输入设备的输入情况,wait_event就是一个定时运行的函数,返回事件类型,主要是检查输入句柄是否有新的数据输入。

keyboard_update 事件判断完毕后,若是有键盘输入,则调用该函数,主要功能就是判断那个按键有输入。数组atic unsigned char state [NR_KEYS] 存储了128个字符的状态,输入哪个字符,这个字符的状态就被置1。

keyboard_getstate 直接返回状态数组

keyboard_suspend

keyboard_resume

移植内容,主要是按键码查表转化

具体移植代码:在Minigui中采用一个比较通用的输入引擎,在内核的键盘驱动中去实现按键的输出来适应Minigui的通用驱动。

4. 输出引擎(New)

具体看framebuffer驱动是如何实现的,FrameBuffer中的数据格式如何。

真彩32bit

Minigui的象素输出格式0x00rrggbb

基于视频芯片的输入要求格式:0xaarrggbb

问题:对于Minigui中的格式高一字节永远是0x00,对该字节不进行处理的。

但视频芯片的高一字节是定义透明度的,若为0x00则全透明,因此没有进行修改的Minigui程序是没有任何屏幕显示的。

解决:需要在Minigui的最终输出部分把高位修改为0xff。

具体代码:待续

二、MiniGui的文件部署

1. 开发环境文件的部署

有上面的安装命令可以看出系统所有的文件都在/opt/usr/local中

/opt/usr/local/

->etc

->(MiniGUI.cfg)

->include

->minigui

->(*.h)

->lib

->(*.a)

->minigui

->res

->bmp

->(系统位图文件)

->cursor

->(系统光标文件)

->font

->(系统字体文件)

->icon

->(系统图标文件)

->imetab

->(输入法文件)



2. Romfs中文件的部署

/opt/usr/local/

->bin

->(应用程序)

->lib

->minigui

->res

->bmp

->(系统位图文件)

->cursor

->(系统光标文件)

->font

->(系统字体文件)

->icon

->(系统图标文件)

->imetab

->(输入法文件)



3.

三、 MiniGui的配置文件(/usr/local/etc/MiniGUI.cfg)

1. 输入输出引擎

IAL= console

若uClinux的输入驱动若是基于tty的就可以这么配置

该引擎的源代码主要在目录\src\ial\native中的native.c 和 native.h文件中。

GAL = fbcon

基于Framebuffer的输出引擎可以这样配置

2. 字库配置

Mingui共支持五种字体分别是:等宽字体( rawbitmap)、变宽字体(varbitmap)、qpf、ttf、type1。因此在配置文件中有五个配置段分别来配置五种字体。

3.

四、Minigui的裁剪

1. 在编译的时候适当的关闭一些不需要的功能

2. 资源的裁剪

主要是系统无用字库、无用图标、图片、光标的删除。

3.

五、应用程序的编译(New)

1. mde例子程序的编译交叉编译

make CC=arm-elf-gcc LDFLAGS="-elf2flt -L/opt/usr/local/lib" \

CFLAGS="-I/opt/usr/local/include "

2.

3. 应用程序运行前处理

设置内容:主要就是堆栈大小的设置。

设置原因:若堆栈太小,程序的函数调用又非常复杂,则容易出现堆栈溢出,从而导致运行错误。

设置方法:使用arm-elf-flthdr –s 16384 pro_name

16384就是堆栈的大小即16×1024 = 16K

使用arm-elf-flthdr –p pro_name 可以查看有关信息

4.

六、

各种License规范的异同

Software

各种License规范的异同
本篇文章属于》IT文摘 , LINUX专区

以下引用 :) 【From Wikibooks, the free textbook project】

各种开源软件授权方式的选择

各种开源软件授权方式的介绍
首先介绍开源软件的共同的特点:源代码开放、免费修改、免费重新发布。

  以BDS为代表的接近于公共域软件的授权。包括Xwindows、freeBDS、apache、perl、python、ruby、zope等。其中 apache的授权叫APL,是一种比较典型的授权声明,下面对于近似公共域的授权以APL表示。这种授权的特点就是虽然保留版权,但不但免费修改、免费重新发布,而且允许商业使用,允许商业修改后不公布修改的软件代码。是对商业软件友好的授权方式。

  以GPL为代表的自由软件,包括linux、gcc、KDE、gnome等。允许免费修改、免费重发布,但要求修改代码必须也遵守GPL。这种授权方式大大限制了从开源中牟利的手段,因此是对商业不友好的授权,对商业不友好的后果是不能使开源代码产生更广泛的效果、不能调动商业软件开发力量。但也要看到GPL对打破垄断的价值,打破垄断对所有的商业软件也是有利的。在GPL下面还有一种对商业更友好的方式就是LGPL,允许商业代码链接LGPL代码,这样商业软件在利用LGPL软件的同时能够很大程度上保留商业利益。gnome是LGPL的(不确定),KDE是GPL的。因此在KDE上面实现商业软件比较困难,因此说KDE是开放不充分的。

  以MPL为代表的商业公司的开源策略。包括mozilla、openoffice等。允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起者,这样发起者和组织者具有更优越的地位。MPL一般也是同时遵守LGPL的。这是因为GPL比较严格,不会产生另一个商业的竞争者。MPL也是对商业友好的。并且用一些优惠来鼓励商业软件开源。

  关于GPL,如果开源软件的开发要借助社区的力量,那么最好是用GPL授权,因为这样可以防止商业软件抢走用户而导致的开源软件的使用者和开发者都不足。

  如果开源软件的开发部需要借助社区的力量,而是封闭开发,使用BSD授权是最恰当的授权。因为既然不需要借助社区的力量,用户的多少和后加入开发的人的多少都没有关系,并且同样可以达到开源软件给用户修改和重新发布的自由。并且:一、如果允许修改者商业化,则更加调动了修改者的积极性,可以弥补开源软件不注重豪华功能的缺陷,使小部分用户的特殊需要也能够得到满足,和开源软件形成互补关系。如activepython就是对python的很好的补充。二、开源软件的使用范围也更广,对社会的贡献也更大。比如现在python被引入到.net支持的语言范围内,而如果python是GPL,则这种情况是不可能发生的。三、比如nextstep,beOS,macX的出现说明BSD使商业软件的起步更高,促进了商业软件的发展,对用户来说获益更大。(我认为开源和商业并不矛盾,而是相互补充,用户都需要)。四、不但对用户有好处,也对软件发展的基石--开发人员有好处。使开源软件的开发者在促进开源的同时还可以使自己的事业得益于开源代码,避免了学习投入的浪费,也使开发的重复工作量最小。(使软件开发在更庞大的基础上继续前进会节省大量的社会资源,会使软件的开发成果更快发展,会出现更多精彩的软件,而每个公司都从基础做起是对社会的更大浪费,这个问题需要政府和更多的公益事业人员意识到,软件这种不同于其他产品的可继承性的特点需要大家注意,如果立法能够把商业软件的著作权保护期缩短,并且强制开源,和专利的情况接近,那么对社会进步的好处会更大,除了对公司垄断获得超额利润有影响外,对正常获利也没有影响)

  当然,选择GPL或BSD授权还和人的价值观有关系,但以开发类型来选择授权方式是比较合理的。如果采用封闭开发,使用BSD也可以达到GPL的效果,而采用社区开发,BSD会对开发团队的成长不利。如果在没有商业化价值的领域,GPL完全没有必要。

  MPL授权是商业软件想要借助社区的力量的产物。

  LGPL对有商业化的友好性和GPL相比是大大提高了,在很多情况下对商业化都没有阻碍,可以说达到了50%的商业化的要求,但有时商业化需要对源代码的彻底修改,因此不能说LGPL百分之百满足商业化要求,LGPL是一个折衷的授权,如果社区开发的软件希望能在更大的范围内被使用,可以采用LGPL。

  各种软件授权的优缺点及适用范围和变种(增订版)

  APL的优点:能够同时和GPL和商业授权相兼容,使APL的软件代码得到最大限度的利用。
APL的适用范围:如果软件要求有更广泛的使用范围,为了成为行业标准,或在使用中只有做修改才能应用,为了不失去商业客户,只能选择APL。
APL的缺点:没有竞争力,用户容易被在APL基础上开发的GPL和商业软件抢走用户。失去用户的结果就是失去社区开发者,因此,APL软件不适合社区开发。
APL的变种:BSD license、ZPL、artist license和APL的条款类似。而变种公共域软件则没有任何要求,包括保留作者名称的要求,标出修改内容的要求和改变软件名程的要求。
APL和公共域类软件有apache、perl、python、ruby、zope、xwindows、tex、freeBSD

  GPL的优点:开放源代码,能够保证开发成果不被商业的竞争对手掠夺,能保证用户的忠诚和稳定的社区开发者来源。
  GPL的缺点:不能商业使用,限制了代码的应用范围,因此,不能获得商业开发对用户的好处。如果软件的用户范围小或软件某些功能的用户范围小、开发量大,就不能保证社区开发者的数量,也就不能获得持续的开发。GPL虽然可以应邀开发某些功能,但不如商业软件经济,因为商业软件可以向多个用户收费。
GPL的适用范围:GPL软件生存的前提是用户数量要大,特殊的开发要求要少,适合通用软件。
GPL的变种:MPL要求所有的修改都将版权无偿归软件的创始人所有,而创始人能决定代码的商业使用或改变授权形式。MPL软件无偿使用。
  GPL软件还有一种重要的变种,就是对个人使用免费,对商业使用付费。其中很多是以GPL的方式出现的,因为GPL不允许链接,象各种库就被禁止商业使用了,这些库如果没有采用LGPL授权,那么它们就自然禁止免费商业使用。比如cygwin、berkleyDB、KDE。还有一些开源软件明确说明禁止商业使用。
GPL软件有gcc、linux、glibc、gnome、open office等。

  非LGPL软件,对商业使用付费的软件有cygwin、berkleyDB、KDE、ghostscript的高版本。
  MPL软件有mozilla、sun的java编译器、vim 。