本人
有一块mini2440 开发板,一直想移植qnx
QNX内核源码下载,是比较早公布的,最新的下载不下来,凑合看吧
如果哪位有最新的qnx源码,请赐给我一份谢谢啦
我创建的qnx专题论坛
http://www.cmeee.com/forum/qnx转文
在ARM9处理器S3C2440上运行QNX本文通过在基于ARM9处理器S3C2440的硬件平台上运行QNX,展现了QNX实时操作系统在通用嵌入式系统平台上的运行情况,论述了QNX操作系统的特点以及它的系统构建、开发、调试和性能,为今后项目中选择合适的嵌入式操作系统提供了更多的参考。
1. QNX实时操作系统简介
QNX实时操作系统是加拿大QSSL公司开发的实时操作系统软件,在国际上享有崇高的声誉,是举世公认的行业领导者。用户中有许多世界著名的公司,如IBM,Cisco,Digital,Motorola,VISA。
从1981年产生至今,QNX的体系结构不断完善。QNX有一个非常小的微内核,内核周边有一批协作进程,提供高层的OS服务。如今,QNX广泛地应用于那些以实时性能、开发灵活性和网络灵活性为首要要求的应用领域。大量已安装的QNX系统证明微内核技术不仅经济上可行而且适合关键性任务的应用,如飞行控制系统、武器控制系统、医疗器械、财政事务处理、数据通信、家用电器等多个领域。这些应用对性能的需求是QNX发展的重要推动力。
QNX对嵌入式实时应用程序是很理想的。它可以被裁剪的很小且能提供多任务处理、线程、驱动优先的抢先进程安排。QNX是基于POSIX标准应用编程接口的,这使得一个熟悉UNIX或Linux的程序员很容易进行程序移植。
QNX独特的效率、模块性和简单性,主要通过两个基本原则来实现:微内核结构和基于消息的进程间通信。
QNX设计成微内核操作系统的真正目的不仅仅是使操作系统本身变小,更重要的是模块化,这种模块化的设计无论在扩展性和健壮性方面都使得操作系统的性能有大大的改观,一个真正的微内核不但对用户程序,而且也对其它OS组件(设备驱动、文件系统等)提供完全的内存保护。
QNX操作系统的结构如图1所示。这一结构看上去更像层次而不像组。这样做的好处在于微内核体系结构可作为一种软件总线动态地插拔操作系统模块。
值得注意的是:QNX的微内核本身可作为独立的核运行,不用其它进程(如进程管理器)存在。
图1
20131103182419250.jpg
微内核只关注一些基本的服务:
(1)线程服务:微内核提供POSIX线程创建原语;
(2)信号服务:微内核提供POSIX信号原语;
(3)消息传递服务:微内核处理贯穿系统的线程间的消息的路由选择;
(4)同步服务:微内核提供POSIX线程同步原语;
(5)时序安排服务:微内核使用实时时序安排算法安排线程的执行;
(6)定时器服务:微内核提供丰富的POSIX定时器服务集。
所有的操作系统服务,除了由微内核本身提供的,都是通过标准程序来处理的。QNX操作系统包含以下主要模块:进程管理器、文件管理器、字符设备管理器、图形用户界面、本地网络管理器、TCP/IP协议等。
2. 目标硬件平台
本文的目标平台基于三星的ARM920T处理器S3C2440,在市面上很常见。S3C2440主频400MHz,最高可以运行到533MHz。目标平台上有64M SDRAM和128M NAND FLASH。显示屏为三星3.5英寸真彩屏。目标平台的对外接口有串口、USB HOST接口、USB Device接口和10M以太网口等。图2是目标平台的图片。
图2
20131103182423062.jpg
3.在目标平台上运行QNX
要在目标平台上运行QNX,需要有操作系统引导程序。目标平台上带有一个叫vivi 的bootloader,能够通过开发主机的USB口传输文件到目标平台的内存并跳转执行,有这个功能就可以引导QNX了。目标平台上的bootloader运行以后,会初始化时钟和内存。时钟和内存的信息我们要记住,因为启动QNX和编写设备驱动的时候要用到。目标平台时钟的配置为CPU核时钟FCLK=400MHz,AHB总线的设备(如SDRAM)时钟HCLK=100MHz,APB总线的设备(如UART)时钟PCLK=50MHz,内存大小是64M字节。
QNX实时操作系统支持的处理器体系结构包括ARM、MIPS、PowerPC和x86。QNX提供了很多处理器平台的BSP包,本文要在ARM9处理器S3C2440上运行QNX,因此选择一个近似的BSP包进行修改。
要在目标平台上运行QNX,需要一个控制台做输入输出,一般是通过编写串口驱动程序的方式实现。另外还要修改内核启动之前处理器相关的初始化代码。完成了这些,一个针对目标平台的最基本的QNX BSP就完成了。本文的目标是实现一个完善的QNX BSP,以便于评测QNX操作系统的更多方面。
操作系统启动之前bootloader一般会传启动参数给内核。比如嵌入式Linux操作系统,传递启动参数是个必须的步骤,不然内核无法启动。对于QNX,可以传递启动参数,也可以不传。
QNX的启动流程是这样的:首先运行启动代码,然后跳转到QNX内核,内核会解析一个启动脚本文件,执行系统初始化和脚本中的驱动加载命令,最后启动一个shell。
本文首先介绍启动代码,因为它完成了内核启动之前最基本的初始化,如存储系统、时钟系统和中断系统等等。
3.1启动代码
QNX系统执行的入口是_start,在cstart.S文件中,用来初始化一个基本的C运行环境。程序执行完_start后,跳转到_main.c文件,执行_main()函数。cstart.S 文件中的vstart 是一个用汇编写的子函数,用来设置页表地址和MMU的属性。该子函数在后面的_main()函数中调用。_main()函数的代码如下:
void _main()
{
shdr = (struct startup_header*)boot_args.shdr_addr;
setup_cmdline();
cpu_startup();
ws_init();
#define INIT_SYSPAGE_SIZE 0x600
init_syspage_memory(ws_alloc(INIT_SYSPAGE_SIZE),INIT_SYSPAGE_SIZE);
if(shdr->imagefs_paddr != 0) {
avoid_ram(shdr->imagefs_paddr,shdr->stored_size);
}
main(_argc, _argv, envv);
//
// Tell the mini-drivers that the next timethey're called, they're
// going to be in the kernel. Also flip thehandler & data pointers
// to the proper values for thatenvironment.
//
mdriver_hook();
//
// Copy the local version of the system pagewe've built to the real
// system page location we allocated ininit_system_private().
//
write_syspage_memory();
//
// Tell the AP's that that the syspage isnow present.
//
smp_hook_rtn();
startnext();
}
_main()函数中的main调用的是main.c中的main()函数。在main()函数中,还是处于实地址模式。在main()函数中完成了很多初始化工作,比如存储系统初始化、时钟系统初始化、中断系统初始化和串口初始化等。
在_main()函数中执行完main()函数以后,最后执行startnext()函数。在startnext()函数中通过调用vstart,真正进入虚地址模式。startnext()函数执行完以后就进入QNX内核了。QNX内核会解析bsp-s3c2440.bsh系统启动脚本,执行系统初始化和脚本中的驱动加载命令,启动应用程序,最后启动一个shell,完成系统启动。
3.1.1存储系统初始化
通过调用init_mmu()函数,对页表进行初始化,等调用vsstart的时候,页表的地址就被设置到MMU了,再通过调用init_raminfo()函数将内存信息(基地址和内存大小)传给系统。
3.1.2时钟系统初始化
QNX的运行需要一个系统时钟,本文利用S3C2440的timer4作为系统定时器。系统时钟的初始化在init_qtime_s3c2440()函数中完成。默认系统时钟的精度是10ms,QNX内核支持系统时钟的精度在1ms到10ms之间可调。
3.1.3中断系统初始化
中断向量表的初始化是在init_cpuinfo.c文件中的init_cpuinfo()函数中完成的。函数的内容如下:
void init_cpuinfo()
{
structcpuinfo_entry *cpu =set_syspage_section(&lsp.cpuinfo, sizeof(*lsp.cpuinfo.p));
unsigned cpuid;
conststruct arm_core_info *core;
conststruct arm_core_config *config;
__asm__("mrc p15, 0, %0, c0, c0, 0"
: "=r" (cpuid)
:
);
cpu->cpu = cpuid;
if(shdr->flags1 & STARTUP_HDR_FLAGS1_VIRTUAL)
cpu->flags |= CPU_FLAG_MMU;
else
cpu->flags = 0;
/*
* Setdefault mmu_control and trap_vectors
*/
mmu_control = ARM_MMU_CR_S | ARM_MMU_CR_L |ARM_MMU_CR_D | ARM_MMU_CR_P
| ARM_MMU_CR_A | ARM_MMU_CR_W | ARM_MMU_CR_C| ARM_MMU_CR_M
| ARM_MMU_CR_X;
trap_vectors = 0xffff0000;
/*
*Work out what core we are running on
*/
core = arm_core_detect(cpuid);
config = core->config;
if(config == 0) {
crash("No core config for%s\n", core->name);
}
cpu->name = add_string(core->name);
mmu_control |= config->mmu_cr;
cycles_per_loop = config->cycles;
/*
* Addvarious callouts
*/
cpu->ins_cache = system_icache_idx;
cpu->data_cache = system_dcache_idx;
arm_add_cache(cpu, config->cache);
add_page_callouts(config);
if(config->power) {
add_callout(offsetof(struct callout_entry, power),config->power);
}
/*
* Doany extra CPU specific initialisation
*/
if(core->extra_init) {
core->extra_init(cpu, cpuid);
}
/*
* Setthe CPU speed
*/
if(cpu_freq != 0) {
cpu->speed = cpu_freq / 1000000;
}
else{
cpu->speed = (cycles_per_loop != 0) ?arm_cpuspeed() : 0;
}
if(debug_flag) {
kprintf("%s rev %d %dMHz\n",core->name, cpuid & 15, cpu->speed);
}
if(shdr->flags1 & STARTUP_HDR_FLAGS1_VIRTUAL) {
int i;
/*
*Allocate and map the trap vector table
*/
paddr32_t pa= calloc_ram(__PAGESIZE, __PAGESIZE);
arm_map(trap_vectors, pa , __PAGESIZE,ARM_PTE_RW | ARM_PTE_CB);
/*
*Set up the trap entry point to be "ldr pc, [pc, #0x18]"
*These jump slot addresses are all zero, so until these have
*been properly set up, any exception will result in a loop.
*/
for(i = 0; i < 8; i++) {
*((unsigned *)pa + i) = 0xe59ff018;
}
}
}
下面对上面的代码做些简单介绍:
mmu_control= ARM_MMU_CR_S | ARM_MMU_CR_L | ARM_MMU_CR_D | ARM_MMU_CR_P
| ARM_MMU_CR_A | ARM_MMU_CR_W |ARM_MMU_CR_C | ARM_MMU_CR_M
| ARM_MMU_CR_X;
trap_vectors= 0xffff0000;
mmu_control是unsigned类型的变量,给mmu_control赋值后,在后面调用vstart函数的时候才写入CP15协处理器的C1寄存器。CP15的C1寄存器是一个控制寄存器,它的bit[13]如果为1,则中断向量表的位置在0xffff0000-0xffff001c,如果为0,则中断向量表的位置在0x00000000-0x0000001c。ARM_MMU_CR_X的值是(1 << 13),所以中断向量表的位置在0xffff0000。
paddr32_t pa = calloc_ram(__PAGESIZE, __PAGESIZE);
arm_map(trap_vectors,pa , __PAGESIZE, ARM_PTE_RW | ARM_PTE_CB);
上面两句是先在内存中分配4k大小的空间,然后调用arm_map在页表中建立中断向量表所在虚拟地址(0xffff0000)和物理地址pa开始的一段大小为__PAGESIZE的内存的映射关系。
for (i = 0; i <8; i++) {
*((unsigned *)pa + i) = 0xe59ff018;
}
上面的循环把中断向量表(每个中断向量占4个字节,共32字节,也就是0x1c个字节)全部填为0xe59ff018,也就是指令"ldr pc, [pc, #0x18]"。此时,中断向量表中并没有有效内容。通过调用函数init_intrinfo()函数完成中断向量表填充。
下面对QNX系统的中断向量表做一个简单介绍。本文用到的中断向量表内容如下:
conststatic struct startup_intrinfo intrs[]= {
{ _NTO_INTR_CLASS_EXTERNAL, // vector base
32, //number of vectors
_NTO_INTR_SPARE, // cascade vector
0, //CPU vector base
0, //CPU vector stride
0, //flags
{ INTR_GENFLAG_LOAD_SYSPAGE, 0, &interrupt_id_s3c2410 },
{ INTR_GENFLAG_LOAD_SYSPAGE |INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_s3c2410 },
&interrupt_mask_s3c2410, // mask callout
&interrupt_unmask_s3c2410, // unmask callout
0, //config callout
0,
},
// UART0 interrupt
{ 32, // vector base
3, //number of vectors
28, // cascade vector
0, //CPU vector base
0, //CPU vector stride
0, //flags
{ 0, 0, &interrupt_id_s3c2410_u0 },
{ INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_s3c2410_u0 },
&interrupt_mask_s3c2410_u0, // mask callout
&interrupt_unmask_s3c2410_u0, // unmask callout
0, //config callout
0,
},
// EINT4-7
{ 35, // vector base
4, //number of vectors
4, // cascade vector
0, //CPU vector base
0, //CPU vector stride
0, //flags
{ 0, 0, &interrupt_id_s3c2410_ext_4_7},
{ INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_s3c2410_ext_4_7 },
&interrupt_mask_s3c2410_ext_4_7, // mask callout
&interrupt_unmask_s3c2410_ext_4_7, // unmask callout
0, //config callout
0,
},
// EINT8-23
{ 39, // vector base
16, //number of vectors
5, // cascade vector
0, //CPU vector base
0, //CPU vector stride
0, //flags
{ 0, 0, &interrupt_id_s3c2410_ext_8_23},
{ INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_s3c2410_ext_8_23 },
&interrupt_mask_s3c2410_ext_8_23, // mask callout
&interrupt_unmask_s3c2410_ext_8_23, // unmask callout
0, //config callout
0,
},
// UART1 interrupt
{ 55, //vector base
3, //number of vectors
23, // cascade vector
0, //CPU vector base
0, //CPU vector stride
0, //flags
{ 0, 0, &interrupt_id_s3c2410_u1 },
{ INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_s3c2410_u1 },
&interrupt_mask_s3c2410_u1, // mask callout
&interrupt_unmask_s3c2410_u1, // unmask callout
0, //config callout
0,
},
// UART2 interrupt
{ 58, // vector base
3, //number of vectors
15, // cascadevector
0, //CPU vector base
0, //CPU vector stride
0, //flags
{ 0, 0, &interrupt_id_s3c2410_u2 },
{ INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_s3c2410_u2 },
&interrupt_mask_s3c2410_u2, // mask callout
&interrupt_unmask_s3c2410_u2, // unmask callout
0, //config callout
0,
},
// ADC_TC interrupt
{ 61, // vector base
2, //number of vectors
31, // cascade vector
0, //CPU vector base
0, //CPU vector stride
0, //flags
{ 0, 0, &interrupt_id_s3c2410_adc_tc},
{ INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_s3c2410_adc_tc },
&interrupt_mask_s3c2410_adc_tc, // mask callout
&interrupt_unmask_s3c2410_adc_tc, // unmask callout
0, //config callout
0,
},
};
QNX支持级联中断。中断向量表中除了第一组中断是第一级中断外,其它组中断都是级联中断,中断号在第一级中断号的基础上递增。中断向量表中要填入的内容还包括每组中断的mask、unmask函数和中断结束函数等。按照s3c2440的芯片手册正确填写中断向量表,调用函数init_intrinfo()初始化中断控制器以后,就可以正常响应中断了。
3.1.4串口初始化
初始化一个可用的串口有助于调试代码,如果串口能够正常打印信息,那么对启动代码的调试将会帮助很大。
4.启动脚本文件与系统构建
QNX内核启动以后,会解释执行一个启动脚本。我们可以编辑启动脚本,设计系统的启动方式、对系统进行配置。我们也可以在系统的集成开发环境下面进行系统配置。QNX系统的集成开发环境如图3所示。
图3
20131103182426484.jpg
QNX操作系统映像中包含二进制程序、共享库、符号链接和动态链接库等等。我们可以把系统所需要的组件以及开机运行的程序都写入启动脚本文件,编译系统映像的时候会自动添加相应的文件。也可以手动添加我们所需要的程序和动态库等等。QNX有个比较好的地方就是,当我们添加一个二进制程序或一个共享库的时候,如果该程序依赖其它程序或者共享库,系统会自动添加。在Linux系统构建的时候,这些东西往往都要靠系统构建人员自己把握,所以经常碰到找不到共享库或者缺少某个服务的情况。QNX的集成开发环境还有一个比较不错的地方是如果我们调用了某个函数,但是忘记了或者不知道该函数在哪个头文件里面,在集成开发环境下可以自动添加头文件,如图4所示。
图4
20131103182429718.jpg
下面以目前目标平台上运行的QNX映像的启动脚本文件为例,介绍一下启动脚本文件,文件内容如下:
# 初始化串口
devc-sers3c2440 -e -F -b115200 -c500000000x50000000,32
reopen
display_msg Welcome to Neutrino on theS3C2440 (ARM 920T core) Board
slogger &
# Start some common servers
pipe &
#####################################################################
# 初始化CS8900网卡和网络协议栈
#####################################################################
display_msg Starting ethernet driver ...
io-net -dcrys8900ioport=0x19000300,irq=40,mac=00e02991234e -ptcpip
#####################################################################
# 通过网络远程调试 (gdb or Momentics)
# 需要开启下面的一些服务
#####################################################################
devc-pty
waitfor /dev/ptyp0 4
waitfor /dev/socket 4
qconn port=8000
# 配置网卡IP地址,和Linux的命令非常相似
ifconfig en0 192.168.1.230
#####################################################################
# 加载NANDFLASH驱动
#####################################################################
display_msg Starting etfs driver...
# 第一次加载驱动加上-e选项表示擦除芯片
fs-etfs-s3c2440 -e -r 4096 -Daddr=0x4e000000
#fs-etfs-s3c2440 -r 4096 -D addr=0x4e000000
#####################################################################
# 加载USBHOST驱动
#####################################################################
display_msg Starting USB driver...
io-usb -d ohci ioport=0x49000000,irq=26&
#####################################################################
# 系统的环境变量,包括共享库搜索路径、二进制程序存放路径
# 图形系统环境变量等
#####################################################################
SYSNAME=nto
TERM=qansi
HOME=/
LD_LIBRARY_PATH=.:/proc/boot:/usr/photon/lib:/dll:/lib:/lib/dll:/usr/lib:/usr/photon/dll:/opt/lib:/tmp/
PATH=.:/tmp:/proc/boot:.:/sbin:/usr/sbin:/usr/bin:/bin:/usr/local/bin:/usr/photon/bin:/opt/bin
PHOTON_PATH=/usr/photon
PHOTON=/dev/photon
PHFONT=/dev/phfont
PROCESSOR=armle
MMEDIA_MIDI_CFG=/etc/config/media/midi.cfg
#####################################################################
# 下面的语句用于开启图形系统
# NOTE: Photon and phfont MUST be startedbefore the GRAPHICS driver
#####################################################################
display_msg Starting Photon
Photon &
waitfor /dev/photon 10
#####################################################################
# 加载LCD驱动
#####################################################################
display_msg Starting graphics driver
io-graphics -ds3c2440xres=320,yres=240,bitpp=16,photon -pphoton
#####################################################################
# 加载USB输入设备驱动
#####################################################################
display_msg starting Input Drivers
io-hid -dusb &
waitfor /dev/io-hid/io-hid 10
devi-hid mouse kbd &
#####################################################################
# 开启QNX图形系统的窗口管理系统
#####################################################################
display_msg Starting Window Manager
pwm &
# 启动shell
[+session] sh &
在这个启动脚本中,我们开启了一些必须的服务、加载了相应的驱动程序,最后启动了一个shell。我们需要的一些二进制程序比如dd、df、cp和cat等都是手动添加到映像当中的。当然这些可以通过写入启动脚本,在编译的时候自动添加到映像中。启动脚本中加载的驱动程序以及相关的配置我们会在后面介绍。
5. QNX操作系统在目标平台上的运行
5.1系统的加载启动
通过USB接口把我们编译好的QNX操作系统映像传到目标平台内存地址0x30008000开始的地方,然后跳转到0x30008000就可以了。
=> load ram0x30008000 3454576 u
USB host isconnected. Waiting a download.
Now, Downloading[ADDRESS:30008000h,TOTAL:3454586]
RECEIVED FILE SIZE:3454586 (843KB/S, 4S)
Downloaded file at0x30008000, size = 3454576 bytes
=> go 0x30008000
go to 0x30008000
argument 0 = 0x00000000
argument 1 = 0x00000000
argument 2 = 0x00000000
argument 3 = 0x00000000
下面是系统的启动信息:
Dcache: 512x32 WB
Icache: 512x32
arm920 rev 0 400MHz
Headersize=0x0000009c, Total Size=0x00000600, #Cpu=1, Type=4
Section:system_privateoffset:0x000001d8 size:0x00000068
Section:qtimeoffset:0x00000148 size:0x00000048
Section:calloutoffset:0x000000a0 size:0x00000048
Section:cpuinfooffset:0x00000190 size:0x00000020
Section:cacheattroffset:0x000005c0 size:0x00000040
Section:meminfooffset:0x00000600 size:0x00000000
Section:asinfooffset:0x00000300 size:0x00000100
Section:hwinfooffset:0x000002b8 size:0x00000048
Section:typed_stringsoffset:0x00000240 size:0x00000020
Section:stringsoffset:0x00000260 size:0x00000058
Section:intrinfooffset:0x00000400 size:0x000001c0
Section:smpoffset:0x00000600 size:0x00000000
Section:pminfooffset:0x00000600 size:0x00000000
Section:mdriveroffset:0x00000600 size:0x00000000
Section:boxinfooffset:0x000001b0 size:0x00000028
Section:cpuoffset:0x00000128 size:0x00000020
System page atphys:30758000 user:fc404000 kern:fc404000
Starting nextprogram at vfe0264e8
Welcome to Neutrinoon the S3C2440 (ARM 920T core) Board
Starting ethernetdriver ...
Starting USBdriver...
Starting Photon
Starting graphicsdriver
starting InputDrivers
Starting WindowManager
#
5.2在shell下面运行命令
# pidin(相当于Linux的ps命令)
pid tid name prio STATE Blocked
1 1 procnto 0f READY
1 2 procnto 255r RECEIVE 1
1 3 procnto 255r RECEIVE 1
1 4 procnto 10r RECEIVE 1
1 5 procnto 10r RECEIVE 1
1 6 procnto 10r RECEIVE 1
1 7 procnto 10r RUNNING
1 8 procnto 10r RECEIVE 1
1 9 procnto 10r RECEIVE 1
2 1 c/boot/devc-sers3c2440 10r RECEIVE 1
3 1 proc/boot/slogger 10r RECEIVE 1
4 1 proc/boot/pipe 10r SIGWAITINFO
4 2 proc/boot/pipe 10rRECEIVE 1
4 3 proc/boot/pipe 10r RECEIVE 1
5 1 proc/boot/io-net 10r SIGWAITINFO
5 2 proc/boot/io-net 21r RECEIVE 5
5 3 proc/boot/io-net 10r RECEIVE 1
5 4 proc/boot/io-net 10r RECEIVE 1
5 5 proc/boot/io-net 20r RECEIVE 9
6 1 proc/boot/devc-pty 10r RECEIVE 1
7 1 proc/boot/qconn 10r SIGWAITINFO
7 2 proc/boot/qconn 10r CONDVAR 11d314
7 3 proc/boot/qconn 10rRECEIVE 1
7 4 proc/boot/qconn 10r RECEIVE 3
4104 1 bin/io-usb 10r SIGWAITINFO
4104 2 bin/io-usb 21r RECEIVE 4
4104 3 bin/io-usb 21r RECEIVE 1
4104 4 bin/io-usb 10r RECEIVE 7
4104 5 bin/io-usb 10r NANOSLEEP
4104 6 bin/io-usb 10r RECEIVE 7
4105 1 proc/boot/Photon 24r RECEIVE 1
4106 1 c/boot/io-graphics 12r RECEIVE 5
4106 2 c/boot/io-graphics 10r RECEIVE 1
4106 3 c/boot/io-graphics 12r REPLY 4105
4107 1 bin/io-hid 10r SIGWAITINFO
4107 2 bin/io-hid 21r RECEIVE 1
4107 3 bin/io-hid 10r RECEIVE 4
4107 4 bin/io-hid 10r RECEIVE 4
4107 5 bin/io-hid 10r REPLY 4104
4108 1 bin/devi-hid 10r DEAD
4108 2 bin/devi-hid 10r REPLY 4107
4108 3 bin/devi-hid 12r SIGWAITINFO
4109 1 proc/boot/pwm 10r RECEIVE 1
4110 1 proc/boot/ksh 10r SIGSUSPEND
4111 1proc/boot/pidin 10r REPLY 1
# ls
.ph bin dev etc lib proc tmp usr
# pwd
/
5.3 QNX集成开发环境对系统监控的一些支持
在目标板上开启qconn网络服务,就可以在IDE环境下监测目标板。qconn是QNX系统自带的一个程序,在系统构建的时候添加到映像中,在启动脚本中让它开机自动运行就可以了。
要对系统进行监测,还需要建一个Target System Project,如图5所示。
20131103182433000.jpg
20131103182436296.jpg
图5
20131103182439703.jpg
图6
图6就是在IDE环境下看到的目标板的信息,包括系统信息、进程信息和存储器分配信息等都可以看到。除此之外,还可以看到目标板的文件系统,并在IDE环境下从目标板拷入、拷出文件和编辑文件,就像在本机一样方便,如图7所示。
20131103182443203.jpg
图7
5.4 QNX的图形系统
QNX有一个非常绚丽的图形系统,图形系统的开发也是在集成开发环境中,开发非常方便。开发过程和难度跟VB非常相似,但是控件的种类和数量比VB要丰富。
跟Vxworks相比,QNX的优势就显现出来了,因为如果不借助于第三方的图形库,在Vxworks下面开发图形操作界面非常不方便,要开发复杂的图形界面非常困难。但是在QNX下面开发复杂的图形界面就非常简单。跟Linux相比,QNX的图形开发也要方便很多。因为Linux下面的图形开发没有一个专门的集成开发环境,开发图形界面不如QNX简单方便。
QNX图形用户界面的开发环境如图8所示,图形程序在目标平台上的实际运行效果如图9所示。
20131103182446328.jpg
图8
20131103182449593.jpg
图9
6. QNX驱动程序设计
QNX操作系统的驱动程序有个特点就是运行在应用层,通过调用ThreadCtl()函数获得IO权限来访问硬件。驱动程序的崩溃不会造成内核的崩溃,驱动程序的开发和调试也比较方便。下面对目标平台上各硬件模块的驱动程序设计进行简单介绍。
6.1串口驱动程序设计
串口驱动程序非常重要,串口驱动完成以后,就可以构建操作系统映像,启动系统,最后启动shell。shell启动以后,就可以对系统进行管理了。此外,还可以用rz程序从串口接收开发主机发送的文件,这就方便了后续驱动程序的开发调试。
串口驱动程序的编写主要是实现QNX字符设备DDK中的几个接口函数。下面介绍几个主要的函数:
void create_device(TTYINIT_S3C2440*dip, unsigned unit);
这个函数获得设备入口和设备的输入输出缓冲区并根据驱动的加载命令创建一个新的设备。
void ser_stty(DEV_S3C2440*dev);
这个函数配置具体的硬件寄存器并设置波特率、奇偶校验等。
int tto(TTYDEV*ttydev, int action, int arg1);
这个函数的主要功能是从设备输出缓冲区中取出数据送往硬件。
static const structsigevent * ser_intr_rx(void *area, int id);
static const structsigevent * ser_intr_tx(void *area, int id);
上面两个函数是串口的接收和发送中断处理程序。
在开发串口驱动程序的时候,遇到了一些问题,主要是对级联中断的使用还不懂。因为S3C2440的串口发送、接收、出错是三个级联中断,级联到第一级中断。所以最初编写串口中断处理程序的时候,只实现了一个第一级中断的中断处理程序,在里面处理中断发送和接收。这样的写法不行,现象是串口接收正常,但是在终端的回显特别慢。后来想到了级联中断的问题,于是用板上外接在EINT0、EINT2、EINT11、EINT19的几个按键测试了中断向量的设置,验证了一级中断以及级联中断的设置(串口的发送、接收、出错这三个中断就是级联中断,级联在第一级的28号中断上),然后分别实现串口的发送、接收中断处理程序,串口就可以正常工作了。
串口驱动的加载命令如下:
devc-sers3c2440 -e-F -b115200 -c50000000 0x50000000,32
devc-sers3c2440是串口驱动程序,-b表示的是串口的波特率设置,此处是设置为115200。-c表示串口设备的输入时钟,此处是50MHz,就是APB CLK。0x50000000是S3C2440串口寄存器的基地址。最后一个32是串口的中断号(串口中断有三个,接收中断号32、发送中断号33和出错中断号34,它们是级联中断)。
串口驱动完成以后,一个最小化的QNX系统就可以运行了。图10是用rz程序从串口接收开发主机发送的文件。如果串口驱动工作正常,就应该可以正常接收文件了,这为后续的驱动开发和调试提供了很大的便利。
20131103182453468.jpg
图10
6.2网口驱动程序设计
本文不对QNX的网络驱动程序做详细介绍,只是说明一下QNX代码的模块化和较好的可重用性。目标平台的网卡芯片用的是CS8900,我们找到了别的目标平台下CS8900网卡的驱动程序。因为CS8900网卡芯片是连接在处理器外部总线上的,通过外部总线访问,所以感觉不用做太多修改就可以工作。但想象不到的是一行代码都不用修改就可以正常工作了。仅仅在startup程序中添加了初始化CS8900所在地址空间总线访问时序的代码和CS8900连接的处理器外部中断的初始化代码:
// cs8900 use EINT9and high level interrupt
// configure EINT9pin as interrupt function
tmp = in32(PGCON);
tmp |= 1<<3;
tmp &=~(1<<2);
out32(PGCON, tmp);
kprintf("PGCON= %x\n", in32(PGCON));
// configure EINT9as rising edge trigger
tmp = in32(EXTINT1);
tmp |= (1<<6);
tmp &=~(1<<5);
out32(EXTINT1, tmp);
kprintf("EXTINT1= %x\n", in32(EXTINT1));
// configure EINT9 notpoll up
tmp = in32(PGUP);
tmp |= 1<<1;
out32(PGUP, tmp);
// bank widthregister, modify for cs8900 bank3
tmp = 0x2211d110;
out32(BWSCON, tmp);
kprintf("BWSCON= %x\n", tmp);
tmp = 0x1f7c;
out32(BANKCON3,tmp);
kprintf("BANKCON3= %x\n", tmp);
网络驱动程序加载的命令如下:
io-net -dcrys8900ioport=0x19000300,irq=40,mac=00e02991234e -ptcpip
ioport表示CS8900的片选地址,irq是CS8900连接的处理器外部中断的中断号。
6.3 NANDFLASH驱动程序设计
目标平台上的NAND FLASH芯片用的是K9F1208。K9F1208有64M字节,含4096blocks,32pages/block,512字节/page。32Í512=16K字节/block,16kÍ4096blocks=64M字节。
驱动涉及到的文件不多,只有两个文件:chipio.c和devio.c。但是功能还是比较强的,主要是因为驱动DDK里面实现了很多功能。本文的主要工作在chipio.c文件中,需要实现对NAND FLASH芯片操作的几个函数:
void nand_write_cmd(structchipio *cio, int command);
往NAND FLASH芯片写入命令。
void nand_write_pageaddr(structchipio *cio, unsigned page, int addr_cycles);
往NAND FLASH芯片写入页地址。
void nand_write_blkaddr(structchipio *cio, unsigned blk, int addr_cycles);
往NAND FLASH芯片写入块地址。
void nand_write_data(structchipio *cio, uint8_t *databuffer, uint8_t *sparebuffer);
往NAND FLASH芯片写入一页数据。
void nand_read_data(structchipio *cio, uint8_t *databuffer, int data_cycles);
从NAND FLASH芯片读出数据。
在驱动实现的时候碰到了两个小问题:
(1)读写NAND FLASH控制器的数据寄存器的问题,因为该寄存器是32bit,本来以为是8bit为单位进行传输,忽略高bit,但实际上是32bit为单位进行传输。
(2)块地址的填充,开始的做法是:
int i;
unsigned addr = blk << 5;
printf("blk0x%x erase\n", blk);
out16(cio->io_base+ NFADDR, 0x00);
for (i = 0; i <addr_cycles-1; i++)
{
out16(cio->io_base + NFADDR, addr &0xFF);
addr >>= 8;
nand_wait_busy(cio, MAX_READ_USEC);
}
这样做是错的,下面这样才是正确的:
int i;
unsigned addr =blk << 5;
printf("blk 0x%x erase\n", blk);
for (i = 0; i < addr_cycles; i++)
{
out16(cio->io_base+ NFADDR, addr & 0xFF);
addr >>=8;
nand_wait_busy(cio,MAX_READ_USEC);
}
通过编写专门的测试程序才发现。
NAND FLASH驱动程序的加载命令如下:
fs-etfs-s3c2440 -e -r 4096 -Daddr=0x4e000000
第一次擦除芯片,所以加上-e选项。以后如果不再擦除芯片了,去掉-e选项就可以了。-r选项表示分区/dev/etfs1的大小,单位是kbytes。第一个分区一般存放的是我们的bootloader,我们要跳过这个区域建立文件系统。addr表示S3C2440 NAND FLASH控制器的寄存器基地址。
加载完NAND FLASH驱动,运行df命令就可以看到ETFS文件系统信息了。
# df
/dev/etfs2 122880 0 122880 0% (/fs/etfs/)
/dev/etfs2 122880 0 122880 0% (/fs/etfs/)
/dev/etfs2 124800 124800 0 100% (/fs/etfs/)
/dev/etfs2 124800 124800 0 100% (/fs/etfs/)
/dev/etfs1 8320 8320 0 100%
/dev/etfs1 8320 8320 0 100%
6.4 USB驱动程序设计
如果USB HOST控制器的实现符合OHCI规范,那么驱动程序的开发不需要写任何代码,这充分体现了QNX代码的高度可重用性。S3C2440的USB HOST控制器是兼容OHCI规范1.0的,因此不需要写任何代码。
加载USB HOST控制器驱动的命令如下:
io-usb -d ohciioport=0x49000000,irq=26 &
ioport是USB HOST控制器的寄存器基地址,irq是USB HOST控制器的中断号。
6.5 LCD驱动程序设计
该驱动是FLAT模式,S3C2440的LCD控制器没有2D/3D加速。驱动的设计主要包含下面几个文件,DDK实现了大部分功能,驱动接口比较简单。
draw.c: 核心绘制函数
mode.c: LCD控制器参数配置(根据屏的参数)
mem.c: 视频帧缓存管理
本文主要的工作在mode.c文件中,需要配置LCD控制器参数,包括屏的分辨率、行场同步信息等。
LCD驱动加载命令如下:
io-graphics –ds3c2440xres=320,yres=240,bitpp=16,photon –pphoton
xres和yres表示屏的分辨率,bitpp表示屏的色彩模式。目标平台的LCD用的是24位真彩屏,但是驱动使用的色彩模式是RGB565,这样可以减少数据量而不损失太多色彩。减少数据量是因为S3C2440芯片LCD控制器的DMA总是在从帧缓存中读取数据刷新显示屏,占用了一定的内存带宽。
7. QNX应用程序调试
应用程序的调试可以在IDE环境下也可以在命令行下面使用GDB。图11是在IDE下面进行调试,图12是在命令行下面用GDB进行调试。
要进行应用程序的调试,目标平台上需要开启pdebug服务,在构建系统的时候把pdebug程序添加到映像中就可以了。
如果要在命令行下面调试,首先进入DOS命令行,切换到要调试的程序所在的目录,运行ksh。在ksh提示符下运行targetqnx 目标板IP:8000,会出现连接到目标板的提示信息。把程序拷贝到目标板的内存目录/tmp,运行命令:
sym 程序名,读取程序的符号信息,然后运行程序,就可以使用GDB的各种命令进行程序调试了。
20131103182456921.jpg
图11
20131103182500656.jpg
图12
8. QNX性能测试
由于时间限制,本文没有在目标平台上对QNX操作系统的进程切换时间、中断响应时间和网络性能等进行测试。下面引用别人在X86平台上的测试结果来说明QNX实时操作系统的性能。进行测试的PC配置为Intel Pentium 300MHz CPU,64M SDRAM。
在中断响应时间和中断调度响应时间测试中,QNX RTOS v6.1和VxWorks AE 1.1的中断反应时间平均值相同,但是在最大值上,VxWorksAE 1.1比QNX RTOS v6.1多2.7微秒。在中断调度反应时间上两个操作系统的差异主要表现在最大值上,QNX RTOS v6.1的最大中断调度反应时间只有2.0微妙,而VxWorks AE 1.1则达到了8.0微妙,是QNX RTOS v6.1的整4倍。
20131103182504109.jpg
图13
线程切换时间测试结果如图13所示,其中,TSL-a-2表示一个进程中有两个线程时线程切换反应的时间,以此类推,TSL-a-10表示有10个线程,TSL-b-128表示不同的进程中,一共有128个线程。最终结果的平均值反映了正常情况下线程切换所需的时间,而最大值则反映了由高级中断或高优先级的线程所造成的线程切换的最大延迟时间。从图中可以看出,当一个进程中有两个线程时,线程间的切换速度,无论是平均值还是最大值已经表现出了差异,尤其是最大值。随着线程数量的增加,这两个值差异也在逐步增加,这表明VxWorks AE 1.1的切换速度越来越明显的慢于QNX RTOS v6.1。当一个进程中包含128个线程时,VxWorks AE 1.1的最大线程切换时间比QNX RTOS v6.1多18.1微妙,平均切换时间也多出了1.1微妙。当这128个线程不在同一个进程中时,VxWorks AE 1.1的最大线程切换时间夸张地增加到了45.7微妙,是QNX RTOS v6.1所用时间的3倍多。
9.结论
本文主要是开发了基于目标平台的QNX BSP,使QNX能够在目标平台上稳定运行,展现了QNX实时操作系统许多方面的特点。由于时间限制,对QNX的一些重要参数,比如进程切换时间、中断响应时间和网络性能等还没有在目标平台上测试。
由于作者水平所限,错误之处在所难免,敬请指正。