嵌入式实验指导书.docx
- 文档编号:27520978
- 上传时间:2023-07-02
- 格式:DOCX
- 页数:99
- 大小:1.19MB
嵌入式实验指导书.docx
《嵌入式实验指导书.docx》由会员分享,可在线阅读,更多相关《嵌入式实验指导书.docx(99页珍藏版)》请在冰豆网上搜索。
嵌入式实验指导书
嵌入式实验指导
第二章基础实验
2.1熟悉Linux开发环境
一、实验目的
熟悉Linux开发环境,学会基于嵌入式Linux开发环境的配置和使用。
使用Linux的armv4l-unknown-linux-gcc编译,使用基于NFS方式的调试、运行,了解嵌入式开发的基本过程。
二、实验内容
本次实验使用RedhatLinux9.0操作系统环境,安装ARM-Linux的开发库及编译器。
创建一个新目录,并在其中编写hello.c和Makefile文件。
学习在Linux下的编程和编译过程,以及ARM开发板的使用和开发环境的设置。
下载已经编译好的文件到目标开发板上运行。
三、预备知识
C语言的基础知识、程序调试的基础知识和方法,Linux的基本操作。
四、实验设备及工具(包括软件调试工具)
硬件:
UP-TECHS2410/P270DVP嵌入式实验平台、PC机Pentium500以上,硬盘10G以上。
软件:
PC机操作系统REDHATLINUX9.0+MINICOM+ARM-LINUX开发环境
五、实验步骤
1、建立工作目录
[root@zxtsmile]#mkdirhello
[root@zxtsmile]#cdhello
2、编写程序源代码
在Linux下的文本编辑器有许多,常用的是vim和Xwindow界面下的gedit等,我们在开发过程中推荐使用vim,用户需要学习vim的操作方法,请参考相关书籍中的关于vim的操作指南。
Kdevelope、anjuta软件的界面与vc6.0类似,使用它们对于熟悉windows环境下开发的用户更容易上手。
实际的hello.c源代码较简单,如下:
#include
main()
{
printf(“helloworld\n”);
}
我们可以是用下面的命令来编写hello.c的源代码,进入hello目录使用vi命令来编辑代码:
[root@zxthello]#vihello.c
按“i”或者“a”进入编辑模式,将上面的代码录入进去,完成后按Esc键进入命令状态,再用命令“:
wq”保存并退出。
这样我们便在当前目录下建立了一个名为hello.c的文件。
3、编写Makefile
要使上面的hello.c程序能够运行,我们必须要编写一个Makefile文件,Makefile文件定义了一系列的规则,它指明了哪些文件需要编译,哪些文件需要先编译,哪些文件需要重新编译等等更为复杂的命令。
使用它带来的好处就是自动编译,你只需要敲一个“make”命令整个工程就可以实现自动编译,当然我们本次实验只有一个文件,它还不能体现出使用Makefile的优越性,但当工程比较大文件比较多时,不使用Makefile几乎是不可能的。
下面我们介绍本次实验用到的Makefile文件。
CC=armv4l-unknown-linux-gcc
EXEC=hello
OBJS=hello.o
CFLAGS+=
LDFLAGS+=–static
all:
$(EXEC)
$(EXEC):
$(OBJS)
$(CC)$(LDFLAGS)-o$@$(OBJS)
clean:
-rm-f$(EXEC)*.elf*.gdb*.o
下面我们来简单介绍这个Makefile文件的几个主要部分:
●CC指明编译器
●EXEC表示编译后生成的执行文件名称
●OBJS目标文件列表
●CFLAGS编译参数
●LDFLAGS连接参数
●all:
编译主入口
●clean:
清除编译结果
注意:
“$(CC)$(LDFLAGS)-o$@$(OBJS)”和“-rm-f$(EXEC)*.elf*.gdb*.o”前空白由一个Tab制表符生成,不能单纯由空格来代替。
与上面编写hello.c的过程类似,用vi来创建一个Makefile文件并将代码录入其中
[root@zxthello]#viMakefile
4、编译应用程序
在上面的步骤完成后,我们就可以在hello目录下运行“make”来编译我们的程序了。
如果进行了修改,重新编译则运行:
[root@zxthello]#makeclean
[root@zxthello]#make
注意:
编译、修改程序都是在宿主机(本地PC机)上进行,不能在MINICOM下进行。
5、下载调试
在宿主PC计算机上启动NFS服务,并设置好共享的目录,具体配置请参照前面第一章第四节中关于嵌入式Linux环境开发环境的建立。
在建立好NFS共享目录以后,我们就可以进入MINICOM中建立开发板与宿主PC机之间的通讯了。
[root@zxthello]#minicom
[/mnt/yaffs]mount-tnfs-onolock192.168.0.56:
/arm2410cl/host
注意:
IP地址需要根据宿主PC机的实际情况修改
成功挂接宿主机的arm2410cl目录后,在开发板上进入/host目录便相应进入宿主机的/arm2410cl目录,我们已经给出了编辑好的hello.c和Makefile文件,它们在/arm2410cl/exp/basic/01_hello目录下。
用户可以直接在宿主PC上编译生成可执行文件,并通过上面的命令挂载到开发板上,运行程序察看结果。
如果不想使用我们提供的源码的话,可以再建立一个NFS共享文件夹。
如/root/share,我们把我们自己编译生成的可执行文件复制到该文件夹下,并通过MINICOM挂载到开发板上。
[root@zxthello]#cphello/root/share
[root@zxthello]#minicom
[/mnt/yaffs]mount-tnfs-onolock192.168.0.56:
/root/share/host
再进入/host目录运行刚刚编译好的hello程序,查看运行结果。
[/mnt/yaffs]cd/host
[/host]./hello
helloworld
注意:
开发板挂接宿主计算机目录只需要挂接一次便可,只要开发板没有重起,就可以一直保持连接。
这样可以反复修改、编译、调试,不需要下载到开发板。
六、思考题
1.Makefile是如何工作的?
其中的宏定义分别是什么意思?
2.2多线程应用程序设计
一、实验目的
Ø了解Linux下多线程程序设计的基本原理。
Ø学习pthread库函数的使用。
二、实验内容
读懂pthread.c的源代码,熟悉几个重要的PTHREAD库函数的使用。
掌握共享锁和信号量的使用方法。
进入/arm2410cl/exp/basic/02_pthread目录,运行make产生pthread程序,使用NFS方式连接开发主机进行运行实验。
三、预备知识
Ø有C语言基础
Ø掌握在Linux下常用编辑器的使用
Ø掌握Makefile的编写和使用
Ø掌握Linux下的程序编译与交叉编译过程
四、实验设备及工具
硬件:
UP-TECHS2410/P270DVP嵌入式实验平台,PC机Pentium500以上,硬盘40G以上,内存大于128M。
软件:
PC机操作系统REDHATLINUX9.0+MINICOM+ARM-LINUX开发环境
五、实验原理及代码分析
1.多线程程序的优缺点
多线程程序作为一种多任务、并发的工作方式,有以下的优点:
1)提高应用程序响应。
这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(timeconsuming)置于一个新的线程,可以避免这种尴尬的情况。
2)使多CPU系统更加有效。
操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3)改善程序结构。
一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
LIBC中的pthread库提供了大量的API函数,为用户编写应用程序提供支持。
2.实验源代码与结构流程图
本实验为著名的生产者-消费者问题模型的实现,主程序中分别启动生产者线程和消费者线程。
生产者线程不断顺序地将0到1000的数字写入共享的循环缓冲区,同时消费者线程不断地从共享的循环缓冲区读取数据。
流程图如图2.2.1所示:
图2.2.1生产者-消费者实验源代码结构流程图
本实验具体代码如下:
/************************************************
*Theclassicproducer-consumerexample.
*Illustratesmutexesandconditions.
*byZoujianguo
*2003-12-22
*************************************************/
#include
#include
#include
#include"pthread.h"
#defineBUFFER_SIZE16
/*设置一个整数的圆形缓冲区*/
structprodcons{
intbuffer[BUFFER_SIZE];/*缓冲区数组*/
pthread_mutex_tlock;/*互斥锁*/
intreadpos,writepos;/*读写的位置*/
pthread_cond_tnotempty;/*缓冲区非空信号*/
pthread_cond_tnotfull;/*缓冲区非满信号*/
};
/*--------------------------------------------------------*/
/*初始化缓冲区*/
voidinit(structprodcons*b)
{
pthread_mutex_init(&b->lock,NULL);
pthread_cond_init(&b->notempty,NULL);
pthread_cond_init(&b->notfull,NULL);
b->readpos=0;
b->writepos=0;
}
/*--------------------------------------------------------*/
/*向缓冲区中写入一个整数*/
voidput(structprodcons*b,intdata)
{
pthread_mutex_lock(&b->lock);
/*等待缓冲区非满*/
while((b->writepos+1)%BUFFER_SIZE==b->readpos){
printf("waitfornotfull\n");
pthread_cond_wait(&b->notfull,&b->lock);
}
/*写数据并且指针前移*/
b->buffer[b->writepos]=data;
b->writepos++;
if(b->writepos>=BUFFER_SIZE)b->writepos=0;
/*设置缓冲区非空信号*/
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
/*--------------------------------------------------------*/
/*从缓冲区中读出一个整数*/
intget(structprodcons*b)
{
intdata;
pthread_mutex_lock(&b->lock);
/*等待缓冲区非空*/
while(b->writepos==b->readpos){
printf("waitfornotempty\n");
pthread_cond_wait(&b->notempty,&b->lock);
}
/*读数据并且指针前移*/
data=b->buffer[b->readpos];
b->readpos++;
if(b->readpos>=BUFFER_SIZE)b->readpos=0;
/*设置缓冲区非满信号*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
returndata;
}
/*--------------------------------------------------------*/
#defineOVER(-1)
structprodconsbuffer;
/*--------------------------------------------------------*/
void*producer(void*data)
{
intn;
for(n=0;n<1000;n++){
printf("put-->%d\n",n);
put(&buffer,n);
}
put(&buffer,OVER);
printf("producerstopped!
\n");
returnNULL;
}
/*--------------------------------------------------------*/
void*consumer(void*data)
{
intd;
while
(1){
d=get(&buffer);
if(d==OVER)break;
printf("%d-->get\n",d);
}
printf("consumerstopped!
\n");
returnNULL;
}
/*--------------------------------------------------------*/
intmain(void)
{
pthread_tth_a,th_b;
void*retval;
init(&buffer);
pthread_create(&th_a,NULL,producer,0);
pthread_create(&th_b,NULL,consumer,0);
/*等待生产者和消费者结束*/
pthread_join(th_a,&retval);
pthread_join(th_b,&retval);
return0;
}
3.主要函数分析:
下面我们来看一下,生产者写入缓冲区和消费者从缓冲区读数的具体流程,生产者首先要获得互斥锁,并且判断写指针+1后是否等于读指针,如果相等则进入等待状态,等候条件变量notfull;如果不等则向缓冲区中写一个整数,并且设置条件变量为notempty,最后释放互斥锁。
消费者线程与生产者线程类似,这里就不再过多介绍了。
流程图如下:
s
图2.2.2生产消费流程图
Ø生产者写入共享的循环缓冲区函数PUT
voidput(structprodcons*b,intdata)
{
pthread_mutex_lock(&b->lock);//获取互斥锁
while((b->writepos+1)%BUFFER_SIZE==b->readpos){
//如果读写位置相同
pthread_cond_wait(&b->notfull,&b->lock);
//等待状态变量b->notfull,不满则跳出阻塞
}
b->buffer[b->writepos]=data;//写入数据
b->writepos++;
if(b->writepos>=BUFFER_SIZE)b->writepos=0;
pthread_cond_signal(&b->notempty);//设置状态变量
pthread_mutex_unlock(&b->lock);//释放互斥锁
}
Ø消费者读取共享的循环缓冲区函数GET
intget(structprodcons*b)
{
intdata;
pthread_mutex_lock(&b->lock);//获取互斥锁
while(b->writepos==b->readpos){//如果读写位置相同
pthread_cond_wait(&b->notempty,&b->lock);
//等待状态变量b->notempty,不空则跳出阻塞。
否则无数据可读。
}
data=b->buffer[b->readpos];//读取数据
b->readpos++;
if(b->readpos>=BUFFER_SIZE)b->readpos=0;
pthread_cond_signal(&b->notfull);//设置状态变量
pthread_mutex_unlock(&b->lock);//释放互斥锁
returndata;
}
4.主要的多线程API
在本程序的代码中大量的使用了线程函数,如pthread_cond_signal、pthread_mutex_init、pthread_mutex_lock等等,这些函数的作用是什么,在哪里定义的,我们将在下面的内容中为大家做一个简单的介绍,并且为其中比较重要的函数做一些详细的说明。
Ø线程创建函数:
intpthread_create(pthread_t*thread_id,__constpthread_attr_t*__attr,
void*(*__start_routine)(void*),void*__restrict__arg)
Ø获得父进程ID:
pthread_tpthread_self(void)
Ø测试两个线程号是否相同:
intpthread_equal(pthread_t__thread1,pthread_t__thread2)
Ø线程退出:
voidpthread_exit(void*__retval)
Ø等待指定的线程结束:
intpthread_join(pthread_t__th,void**__thread_return)
Ø互斥量初始化:
pthread_mutex_init(pthread_mutex_t*,__constpthread_mutexattr_t*)
Ø销毁互斥量:
intpthread_mutex_destroy(pthread_mutex_t*__mutex)
Ø再试一次获得对互斥量的锁定(非阻塞):
intpthread_mutex_trylock(pthread_mutex_t*__mutex)
Ø锁定互斥量(阻塞):
intpthread_mutex_lock(pthread_mutex_t*__mutex)
Ø解锁互斥量:
intpthread_mutex_unlock(pthread_mutex_t*__mutex)
Ø条件变量初始化:
intpthread_cond_init(pthread_cond_t*__restrict__cond,
__constpthread_condattr_t*__restrict__cond_attr)
Ø销毁条件变量COND:
intpthread_cond_destroy(pthread_cond_t*__cond)
Ø唤醒线程等待条件变量:
intpthread_cond_signal(pthread_cond_t*__cond)
Ø等待条件变量(阻塞):
intpthread_cond_wait(pthread_cond_t*__restrict__cond,pthread_mutex_t*__restrict__mutex)
Ø在指定的时间到达前等待条件变量:
intpthread_cond_timedwait(pthread_cond_t*__restrict__cond,
pthread_mutex_t*__restrict__mutex,__conststructtimespec*__restrict__abstime)
PTHREAD库中还有大量的API函数,用户可以参考其他相关书籍。
下面我们对几个比较重要的函数做一下详细的说明:
pthread_create线程创建函数
intpthread_create(pthread_t*thread_id,__constpthread_attr_t*__attr,
void*(*__start_routine)(void*),void*__restrict__arg)
线程创建函数第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。
这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。
第二个参数我们也设为空指针,这样将生成默认属性的线程。
当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。
前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。
创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
pthread_join函数用来等待一个线程的结束。
函数原型为:
intpthread_join(pthread_t__th,void**__thread_return)
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。
这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
pthread_exit函数
一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。
它的函数原型为:
voidpthread_exit(void*__retval)
唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。
最后要说明的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式 实验 指导书