您的位置:首页 > 其它

嵌入式实验报告(OK6410环境下的设备驱动及进程间通信 综合实验)

2013-01-23 16:04 369 查看
一、考试内容简介

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 <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <sys/shm.h>

#include <sys/stat.h>

#include <fcntl.h>

#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);

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: