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

一、考试内容简介

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

转载于:https://www.cnblogs.com/smile-ls/archive/2013/01/23/2873313.html

你可能感兴趣的:(嵌入式实验报告(OK6410环境下的设备驱动及进程间通信 综合实验))