微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > 嵌入式Linux的ARM移植实例研究

嵌入式Linux的ARM移植实例研究

时间:04-16 来源:3721RD 点击:

应用实例的编写实际上已经不属于Linux操作系统移植的范畴,但是为了保证本系列文章的完整性,这里提供一系列针对嵌入式Linux开发应用程序的实例。

编写Linux应用程序要用到如下工具:

(1)编译器:GCC

GCC是Linux平台下最重要的开发工具,它是GNU的C和C++编译器,其基本用法为:gcc[options][filenames]。
我们应该使用arm-linux-gcc。

(2)调试器:GDB

gdb是一个用来调试C和C++程序的强力调试器,我们能通过它进行一系列调试工作,包括设置断点、观查变量、单步等。
我们应该使用arm-linux-gdb。

(3)Make

GNUMake的主要工作是读进一个文本文件,称为makefile。这个文件记录了哪些文件由哪些文件产生,用什么命令来产生。Make依靠此makefile中的信息检查磁盘上的文件,如果目的文件的创建或修改时间比它的一个依靠文件旧的话,make就执行相应的命令,以便更新目的文件。

Makefile中的编译规则要相应地使用arm-linux-版本。

(4)代码编辑

可以使用传统的vi编辑器,但最好采用emacs软件,它具备语法高亮、版本控制等附带功能。

在宿主机上用上述工具完成应用程序的开发后,可以通过如下途径将程序下载到目标板上运行:

(1)通过串口通信协议rz将程序下载到目标板的文件系统中(感谢Linux提供了rz这样的一个命令);
(2)通过ftp通信协议从宿主机上的ftp目录里将程序下载到目标板的文件系统中;
(3)将程序拷入U盘,在目标机上mountU盘,运行U盘中的程序;
(4)如果目标机Linux使用NFS文件系统,则可以直接将程序拷入到宿主机相应的目录内,在目标机Linux中可以直接使用。

1.文件编程

Linux的文件操作API涉及到创建、打开、读写和关闭文件。

创建

intcreat(constchar*filename,mode_tmode);
参数mode指定新建文件的存取权限,它同umask一起决定文件的最终权限(mode&umask),其中umask代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:
intumask(intnewmask);
该调用将umask设置为newmask,然后返回旧的umask,它只影响读、写和执行权限。

打开

intopen(constchar*pathname,intflags);
intopen(constchar*pathname,intflags,mode_tmode);

读写

在文件打开以后,我们才可对文件进行读写了,Linux中提供文件读写的系统调用是read、write函数:
intread(intfd,constvoid*buf,size_tlength);
intwrite(intfd,constvoid*buf,size_tlength);

其中参数buf为指向缓冲区的指针,length为缓冲区的大小(以字节为单位)。函数read()实现从文件描述符fd所指定的文件中读取length个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。函数write实现将把length个字节从buf指向的缓冲区中写到文件描述符fd所指向的文件中,返回值为实际写入的字节数。
以O_CREAT为标志的open实际上实现了文件创建的功能,因此,下面的函数等同creat()函数:
intopen(pathname,O_CREAT|O_WRONLY|O_TRUNC,mode);

定位

对于随机文件,我们可以随机的指定位置读写,使用如下函数进行定位:
intlseek(intfd,offset_toffset,intwhence);
lseek()将文件读写指针相对whence移动offset个字节。操作成功时,返回文件指针相对于文件头的位置。参数whence可使用下述值:
SEEK_SET:相对文件开头
SEEK_CUR:相对文件读写指针的当前位置
SEEK_END:相对文件末尾
offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5个字节:
lseek(fd,-5,SEEK_CUR);
由于lseek函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就是文件的长度:
lseek(fd,0,SEEK_END);

关闭

只要调用close就可以了,其中fd是我们要关闭的文件描述符:
intclose(intfd);
下面我们来编写一个应用程序,在当前目录下创建用户可读写文件"example.txt",在其中写入"HelloWorld",关闭文件,再次打开它,读取其中的内容并输出在屏幕上:
#include
#include
#include
#include
#defineLENGTH100
main()
{
intfd,len;
charstr[LENGTH];
fd=open("hello.txt",O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);/*创建并打开文件*/
if(fd)
{
write(fd,"Hello,SoftwareWeekly",strlen("Hello,softwareweekly"));
/*写入Hello,softwareweekly字符串*/
close(fd);
}
fd=open("hello.txt",O_RDWR);
len=read(fd,str,LENGTH);/*读取文件内容*/
str[len]='';
printf("%sn",str);
close(fd);
}

2.进程控制/通信编程

进程控制中主要涉及到进程的创建、睡眠和退出等,在Linux中主要提供了fork、exec、clone的进程创建方法,sleep的进程睡眠和exit的进程退出调用,另外Linux还提供了父进程等待子进程结束的系统调用wait。
fork
对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一,因为它执行一次却返回两个值,以前"闻所未闻"。先看下面的程序:
intmain()
{
inti;
if(fork()==0)
{
for(i=1;i<3;i++)
printf("Thisischildprocessn");
}
else
{
for(i=1;i<3;i++)
printf("Thisisparentprocessn");
}
}

执行结果为:
Thisischildprocess
Thisischildprocess
Thisisparentprocess
Thisisparentprocess

fork在英文中是"分叉"的意思,一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就"分叉"了。当前进程为父进程,通过fork()会产生一个子进程。对于父进程,fork函数返回子程序的进程号而对于子程序,fork函数则返回零,这就是一个函数返回两次的本质。

exec

在Linux中可使用exec函数族,包含多个函数(execl、execlp、execle、execv、execve和execvp),被用于启动一个指定路径和文件名的进程。exec函数族的特点体现在:某进程一旦调用了exec类函数,正在执行的程序就被干掉了,系统把代码段替换成新的程序(由exec类函数执行)的代码,并且原有的数据段和堆栈段也被废弃,新的数据段与堆栈段被分配,但是进程号却被保留。也就是说,exec执行的结果为:系统认为正在执行的还是原先的进程,但是进程对应的程序被替换了。

fork函数可以创建一个子进程而当前进程不死,如果我们在fork的子进程中调用exec函数族就可以实现既让父进程的代码执行又启动一个新的指定进程,这很好。fork和exec的搭配巧妙地解决了程序启动另一程序的执行但自己仍继续运行的问题,请看下面的例子:
charcommand[MAX_CMD_LEN];

voidmain()
{
intrtn;/*子进程的返回数值*/
while(1)
{
/*从终端读取要执行的命令*/
printf(">");
fgets(command,MAX_CMD_LEN,stdin);
command[strlen(command)-1]=0;
if(fork()==0)
{
/*子进程执行此命令*/
execlp(command,command);
/*如果exec函数返回,表明没有正常执行命令,打印错误信息*/
perror(command);
exit(errorno);
}
else
{
/*父进程,等待子进程结束,并打印子进程的返回值*/
wait(&rtn);
printf("childprocessreturn%dn",rtn);
}
}
}
这个函数实现了一个shell的功能,它读取用户输入的进程名和参数,并启动对应的进程。

clone

clone是Linux2.0以后才具备的新功能,它较fork更强(可认为fork是clone要实现的一部分),可以使得创建的子进程共享父进程的资源,并且要使用此函数必须在编译内核时设置clone_actually_works_ok选项。

clone函数的原型为:
intclone(int(*fn)(void*),void*child_stack,intflags,void*arg);
此函数返回创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项。
来看下面的例子:
intvariable,fd;
intdo_something(){
variable=42;
close(fd);
_exit(0);
}
intmain(intargc,char*argv[]){
void**child_stack;
chartempch;
variable=9;
fd=open("test.file",O_RDONLY);
child_stack=(void**)malloc(16384);
printf("Thevariablewas%dn",variable);
clone(do_something,child_stack,CLONE_VM|CLONE_FILES,NULL);
sleep(1);/*延时以便子进程完成关闭文件操作、修改变量*/
printf("Thevariableisnow%dn",variable);
if(read(fd,&tempch,1)<1){
perror("FileReadError");
exit(1);
}
printf("Wecouldreadfromthefilen");
return0;
}
运行输出:
Thevariableisnow42
FileReadError
程序的输出结果告诉我们,子进程将文件关闭并将变量修改(调用clone时用到的CLONE_VM、CLONE_FILES标志将使得变量和文件描述符表被共享),父进程随即就感觉到了,这就是clone的特点。

sleep

函数调用sleep可以用来使进程挂起指定的秒数,该函数的原型为:
unsignedintsleep(unsignedintseconds);
该函数调用使得进程挂起一个指定的时间,如果指定挂起的时间到了,该调用返回0;如果该函数调用被信号所打断,则返回剩余挂起的时间数(指定的时间减去已经挂起的时间)。

exit

系统调用exit的功能是终止本进程,其函数原型为:
void_exit(intstatus);
_exit会立即终止发出调用的进程,所有属于该进程的文件描述符都关闭。参数status作为退出的状态值返回父进程,在父进程中通过系统调用wait可获得此值。

wait

wait系统调用包括:
pid_twait(int*status);
pid_twaitpid(pid_tpid,int*status,intoptions);
wait的作用为发出调用的进程只要有子进程,就睡眠到它们中的一个终止为止;waitpid等待由参数pid指定的子进程退出。

Linux的进程间通信(IPC,InterProcessCommunication)通信方法有管道、消息队列、共享内存、信号量、套接口等。套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几乎是完全一样的。管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间;消息队列用于运行于同一台机器上的进程间通信,与管道相似;共享内存通常由一个进程创建,其余进程对这块内存区进行读写;信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。

下面是一个使用信号量的例子,该程序创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后清除信号量:

#include
#include
#include
#include
voidmain()
{
key_tunique_key;/*定义一个IPC关键字*/
intid;
structsembuflock_it;
unionsemunoptions;
inti;
unique_key=ftok(".",'a');/*生成关键字,字符'a'是一个随机种子*/
/*创建一个新的信号量集合*/
id=semget(unique_key,1,IPC_CREAT|IPC_EXCL|0666);
printf("semaphoreid=%dn",id);
options.val=1;/*设置变量值*/
semctl(id,0,SETVAL,options);/*设置索引0的信号量*/
/*打印出信号量的值*/
i=semctl(id,0,GETVAL,0);
printf("valueofsemaphoreatindex0is%dn",i);
/*下面重新设置信号量*/
lock_it.sem_num=0;/*设置哪个信号量*/
lock_it.sem_op=-1;/*定义操作*/
lock_it.sem_flg=IPC_NOWAIT;/*操作方式*/
if(semop(id,&lock_it,1)==-1)
{
printf("cannotlocksemaphore.n");
exit(1);
}
i=semctl(id,0,GETVAL,0);
printf("valueofsemaphoreatindex0is%dn",i);
/*清除信号量*/
semctl(id,0,IPC_RMID,0);
}

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top