一、考试内容简介
1、采用生产者-消费者模型,控制OK6410的led灯的显示。生产者每秒产生一个0~15数字,放入共享缓冲区;消费者每秒从共享缓冲区取出一个数字,并用该数字设置OK6410的led灯的显示。
2、考试目的
3、掌握进程同步原理及Linux同步机制的编程
4、掌握进程间通信原理及Linux进程间通信的编程
5、掌握设备驱动原理及Linux设备驱动机制的编程
6、掌握操作系统调用原理及Linux系统调用的编程
7、掌握嵌入式开发环境的搭建
8、配置交叉编译工具链
9、配置nfs服务器和共享文件夹
10、配置OK6410开发板的IP网络地址
11、OK6410开发板的arm Linux 3.0.1内核的编译
12、配置minicom终端及串口通信
13、设备驱动模块的加载及编程接口
14、Linux命令的使用和程序的运行(包含后台运行)
二、准备硬件环境
高性能PC一台
开发板一块
计算机网络
USB转串口
网线
三、准备软件环境
Linux操作系统
四、实验原理
①进程通信原理
OS提供了沟通的媒介供进程之间“对话”用。既然要沟通,如同人类社会的沟通一样,沟通要付出时间和金钱,计算机中也一样,必然有沟通需要付出的成 本。出于所解决问题的特性,OS提供了多种沟通的方式,每种方式的沟通成本也不尽相同,使用成本和沟通效率也有所不同。我们经常听到的 管道、消息队列、共享内存都是OS提供的供进程之间对话的方式。
既然是沟通,必然是沟通双方有秩序的说话,否则就成吵架了,谁也听不到对方说什么。如同法庭中法官控制控辩双方的发言时机和发言时间一样,OS也必须提供 此类的管制方式使得进程的沟通显的有序和谐。我们经常听到的 互斥锁、条件变量、记录锁、文件锁、信号灯均属此列。
②生产者和消费者问题原理
生产者-消费者问题是一个经典的进程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制。在同一个进程地址空间内执行的两个线程生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来。如果将两个wait操作即wait(full)和wait(mutex)互换位置,或者将release(mutex)与release(full)互换 位置,当缓冲区存满产品时,生产者又生产了一件产品,它欲向缓冲区存放时将在empty上等待,但它已经占有了使用缓冲区的权利。这时消费者要取产品时将 停留在mutex上得不到使用缓冲区的权利,导致生产者等待消费者取走产品,而消费者却在等待生产者释放使用缓冲区的权利,这种相互等待永远结束不了。因此进程将会发生死锁。
五、把登录系统后的终端的默认用户改为root。
1、在终端中输入:sudo gedit /etc/gdm/custom.conf
2、这时会弹出文本编辑器,将‘custom.conf’内容修改成下面所示内容(若原来文件为空的话就输入这些内容),保存关闭,重新启动Ubuntu 就会发现已经自动用root 用户登录了。
[daemon]
TimedLoginEnable=true
AutomaticLoginEnable=true
TimedLogin=root
AutomaticLogin=root
TimedLoginDelay=30
五、按照飞凌新版光盘A的用户手册6-5安装交叉编译工具链
将arm-linux-gcc-4.3.2.tgz 文件拷贝到Ubuntu 的/forlinx 目录下,该文件位于用户基
础资料光盘的“实用工具”文件夹中。在Ubuntu 中新建一个终端,输入下面的命令安装交叉
编译器:
cd /forlinx (进入/forlinx 目录)
mkdir /usr/local/arm (创建目录,若目录已存在会提示错误,跳过即可)
tar zxvf arm-linux-gcc-4.3.2.tgz -C /
编译器解压到/usr/local/arm
把交叉编译器路径添加到系统环境变量中,以后可以直接在终端窗口中输入arm-linx-gcc 命令来编译程序。
在终端中执行:gedit /etc/profile
添加以下四行到该文件中:
export PATH=/usr/local/arm/4.3.2/bin:$PATH
export TOOLCHAIN=/usr/local/arm/4.3.2
export TB_CC_PREFIX=arm-linuxexport
PKG_CONFIG_PREFIX=$TOOLCHAIN/arm-none-linux-gnueabi
保存,退出。
重新启动系统,在终端里面执arm-linux-gcc –v
六、编译内核
编译 Linux-3.0.1
将压缩包‘FORLINX_linux-3.0.1.tar.gz’ 拷贝到你的工作目录下,解压缩:
tar zxf FORLINX_linux-3.0.1.tar.gz
在终端执行:make
编译结束后将在内核源码目录的arch/arm/boot 中得到Linux 内核映像文件:zImage
七、NFS挂载网络文件系统
1、准备NFS文件系统目录
启动nfs 服务之前,必须在Ubuntu 上准备好NFS 共享目录。
例如,我们采用Ubuntu 的“/forlinx/root”作为NFS 共享目录,就需要将用户基础资料
光盘中的FileSystem-Yaffs2.tar.gz 压缩文件拷贝到这个目录下,然后解压缩,得到根
文件系统所需要的目录。
在Ubuntu 上打开一个终端,输入以下命令:mkdir /forlinx/root
将FileSystem-Yaffs2.tar.gz 文件拷贝到该目录下,解压:
tar –zxf FileSystem-Yaffs2.tar.gz
解压完成后如图所示:
2. 配置NFS服务
在Ubuntu 上新建一个终端,依次输入以下命令:
sudo yum install portmap
sudo yum install nfs-utils
sudo gedit /etc/exports
在弹出的文本编辑器中编辑exports 文件,在最后一行添加:
/forlinx *(rw,sync,no_root_squash)
3. 启动NFS服务
Service portmap start
Service nfs start
4 检查服务是否已经运行
service rpcbind status
service nfs status
在终端里面执行ifconfig命令,查看fedaro的IP地址。
开发板的IP地址,要修改与PC的IP地址在一个局域网里。
5、连接到开发板
由于我用的是Fedaro,而不是Ubuntu,所以我在linux系统下安装了minicom,相当于windows下的secureCRT.
其操作过程如下:
①安装
②安装好之后,执行下面的命令:minicom -s
③设置选项
选择Serial port setup,将A改为/dev/ttyUSB0,其他不变
修改后,保存返回。再进入Modem and dialing,删除A、B、K的参数。
④、若/dev目录下有ttyUSB0,则安装成功。
如何实现minicom与secureCRT相同的功能?
在终端输入:minicom+回车,立即打开开发板,即可实现。
注意:minicom命令必须在root权限下才能执行。
6、Ping网观查看是否有连接
在开发板上执行下面一条命令:
mkdir temp
mount -t nfs -o nolock 192.168.100.197:/forlinx /temp
查看终端中的forlinx中的文件:
八、执行led驱动程序
1、修改Makefile文件
在终端中执行:cd /forlinx/led
执行:gedit Makefile
修改代码,使KDIR :=/forlinx/linux-3.0.1
保存,关闭。
2、执行:make,产生led.ko 文件
3、进行模块加载:insmod *.ko(非常重要)
arm-linux-gcc –o app-led app-led.c
4、执行LED驱动程序
在终端执行minicom,并重新打开开发板:cd test
再执行:cd /led
执行:./app-led 1010
执行:./app-led 1110
九、编写生产者与消费者代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led.h"
#define DEV_NAME "/dev/" DEVICE_NAME
#define SIZE 16 //定义缓冲大小为16个字符
int creat_sem(key_t key,int value); //声明创建信号量初值为value的函数
int sem_p(int sem_id); //声明P操作函数
int sem_v(int sem_id); //声明V操作函数
union semun{ //自定义信号量操作所需要的联合体
int val;
struct semid_ds *buf;
unsigned short *array;
};
void main(){
char *shm_addr=NULL;
struct shmid_ds shm_buf;
pid_t pid;
int key,key1,key2;
int shm_id,full,empty;
static int head=0;
static int tail=0;
int ch=0,mm;
int fd, ioarg;
key=ftok("/tmp",1);
key1=ftok("/opt",1);
key2=ftok("./",1);
full=creat_sem(key1,0); //创建一个初值为0的信号量full
empty=creat_sem(key2,SIZE); //创建一个初值为SIZE的信号量empty
shm_id=shmget(key,SIZE,IPC_CREAT |0666); //创建一个大小为SIZE个字节的共享内存区
shm_addr=shmat(shm_id,NULL,0); //获得共享内存区首地址
memset(shm_addr,0,SIZE); //初始化共享内存区为空
pid=fork(); //创建两个进程
if(pid==-1){ //出错处理
printf("fork error\n");
exit(1);
}
else if(pid>0) //生产者进程
while(1){
sem_p(empty); //判断是否有空闲缓冲区
shm_addr[head%SIZE]=(char)ch++;
if(ch==SIZE){ch=0;}
printf("Producer put %d to shm_addr[%d]\n",(int)(shm_addr[head%SIZE]),head%SIZE); //输出写入的数据及数据对应地址
head++; //指向下一个缓冲区单元
sleep(2);
sem_v(full); //使可用资源数加1
}
else
while(1){ //消费者进程
sem_p(full); //判断缓冲区是否有数据可读
printf("Consumer get %d from shm_addr[%d]\n",(int)(shm_addr[tail%SIZE]),tail%SIZE); //输出读出的数据及数据对应的地址
mm=(int)(shm_addr[tail%SIZE]);
if (-1==(fd=open (DEV_NAME, O_RDWR))) { //指明设备的名字,是读写还是操作
printf("open dev error\n");
_exit(EXIT_FAILURE);
}
ioarg = mm;
printf("ioarg=%d\n", ioarg);
ioctl(fd, LED_IOCSETDAT, &ioarg);
tail++; //指向下一个缓冲区单元
sleep(1);
sem_v(empty); //使空闲缓冲区数加1
}
}
int creat_sem(key_t key,int value){ //创建信号量函数定义
union semun sem_union;
int sem_id;
sem_union.val =value;
sem_id=semget(key,1,IPC_CREAT | 0666);
if(sem_id==-1){
printf("create semaphore error\n");
exit(1);
}
semctl(sem_id,0,SETVAL,sem_union);
return sem_id;
}
int sem_p(int sem_id){ //P操作定义
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id,&sem_b,1) == -1){
printf("semaphore_p failed\n");
exit(1);
}
return (1);
}
int sem_v(int sem_id){ //V操作定义
struct sembuf sem_b;
sem_b.sem_num =0;
sem_b.sem_op = +1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id,&sem_b,1) == -1){
printf("semaphore_v failed\n");
return (0);
}
return (1);
}
在虚拟机中执行:arm-linux-gcc –o CP CP.c
在开发板上执行:./CP
观察LED灯随机的亮!
十、三个问题
7、 解释led驱动程序的led.c中的【struct resource ok6410_led_resource】的各项成员。 struct resource ok6410_led_resource = { .name = "led io-mem",//led对应GPIOM所占资源的名字 .start = GPIOM_PA_BASE,// led对应GPIOM所占资源的起始地址 .end = GPIOM_PA_BASE + 0xc, //led对应GPIOM所占资源的终止地址 .flags = IORESOURCE_MEM,// led对应GPIOM所占资源的类型 }; |
14、详细解释led驱动程序的led.c中的【static int __init dev_init(void)】函数。 将MISC_DYNAMIC_MINOR赋值给miscdevice结构的minor成员, 表示自动分配次设备号, 在使用misc_register()注册混杂设备后, 还会在/dev目录下自动创建设备节点,节点名称由 设备名称DEVICE_NAME指定。在模块初始化函数中, 我们还需要对设备进行必要的初始化。 这里设计一个用于初始化设备的函数接口ok6410_led_pin_setup(), 后面再来实现它。 模块卸载函数完成与初始化函数相反的工作。 |
26、在消费者的程序实现中,哪个部分是临界区? //输出读出的数据及数据对应的地址 printf("Consumer get %d from shm_addr[%d]\n",(int)(shm_addr[tail%SIZE]),tail%SIZE); mm=(int)(shm_addr[tail%SIZE]); //指明设备的名字,是读写还是操作 if (-1==(fd=open (DEV_NAME, O_RDWR))) { printf("open dev error\n"); _exit(EXIT_FAILURE); }
ioarg = mm; printf("ioarg=%d\n", ioarg); ioctl(fd, LED_IOCSETDAT, &ioarg);
tail++; //指向下一个缓冲区单元 sleep(1); |