20145227&20145201 《信息安全系统设计基础》实验二 固件开发
北京电子科技学院(BESTI)
实 验 报 告
课程:信息安全系统设计基础 班级:1452
姓名:(按贡献大小排名)鄢曼君 李子璇
学号:(按贡献大小排名)20145227 20145201
成绩: 指导教师:娄嘉鹏 实验日期:2016.11.3
实验密级:无 预习程度:已预习 实验时间:10:00-12:30
仪器组次: 必修/选修:必修 实验序号:02
实验名称:固件开发
实验目的与要求:
1.学习、读懂 02_pthread 03_tty中的代码;
2.熟悉 linux 开发环境,学会基于 S3C2410 的 linux 开发环境的配置和使用。
3.使用linux的armv4l-unknown-linux-gcc编译,使用基于NFS方式的下载调试,了解嵌入式开发的基本过程。
4.正确使用连接线等实验仪器,并注意保护实验箱。实验结束之后将实验箱送回。
实验仪器:
名称 | 型号 | 数量 |
---|---|---|
arm | UP-TECH | 1 |
pc | Windows XP | 1 |
虚拟机 | redhat | 1 |
实验原理
1、在Linux PC上,利用arm-linux-gcc编译器,可编译出针对Linux ARM平台的可执行代码。
2、了解多线程程序设计的基本原理。
3、学习pthread库函数的使用。
实验内容与步骤
1、配置开发环境(同实验一)
- 连接arm开发板
- 建立超级终端
- 启动实验平台(redhat虚拟机)
- 配置同网段IP
- 安装arm编译器(bc共享文件夹)
- 配置环境变量(redhat虚拟机中)
2、导入实验代码
- 将实验所需代码拷贝到bc共享文件夹中 (实验代码在老师提供的02_pthread和03_tty文件夹中)
3、在虚拟机中编译代码
- 输入命令:armv4l-unknown-linux-gcc pthread.c -o pthread -lpthread
- 对于多线程相关的代码,编译时需要加-lpthread 的库。
4、下载调试
- 在超级终端中运行可执行文件pthread:
- 在超级终端中运行可执行文件term。在执行
./term
时出现下面的错误No such file or directory
,我们通过建立一个连接来解决了,输入命令ln –sf /dev/tts/0 /dev/ttyS0
(注意空格和字母大小写)
- 运行结果如下,可按ctrl+c终止。
实验过程中遇到的问题及解决过程
问题:在超级终端运行可执行文件term时出错,提示/dev/ttyS0: No such file or directory
解决方法:老师给的实验指导书中说:这个问题是因为在 Linux 下串口文件位于/dev 下,一般在老版本的内核中串口一为/dev/ttyS0 ,串口二为 /dev/ttyS1, 在我们的开发板中串口设备位于/dev/tts/下,因为开发板中没有ttyS0这个设备,所以我们要建立一个连接。解决方法就是按照实验指导书说的在超级终端中进入/dev文件夹中,输入命令ln –sf /dev/tts/0 ttyS0
(注意空格与字母l、数字0。)
实验过程的理解与分析
(一)进程相关函数
- 线程在运行过程中需要和其他线程进行交互,在资源不能满足时,需要暂时挂起以等待其他线程正在使用的资源。这种机制称为线程之间同步。线程同步的方法主要有互斥锁、条件变量和信号量。
1.创建进程
创建线程:pthread_create
头文件:#include<pthread.h>
函数原型:int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
获取线程ID:pthread_self
头文件:#include<pthread.h>
函数原型:pthread_t pthread_self(void);
判断2个线程ID是否指向同一线程:pthread_equal
头文件:#include<pthread.h>
函数原型:int pthread_equal(pthread_t thread1,pthread_t thread2);
用于保证init_routine(自己创建的线程)线程函数在进程中仅调用一次,无法再此调用:pthread_once
头文件:#include<pthread.h>
函数原型:int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));
2.终止进程
线程终止:pthread_exit
头文件:#include<pthread.h>
函数原型:void pthread_exit(void *retval);
用于自动释放资源函数:pthread_cleanup_push(),pthread_cleanup_pop()
头文件:#include<pthread.h>
函数原型:#define pthread_cleanup_push(routine,arg)\
{struct _pthread_cleanup_buffer buffer;\
_pthread_cleanup_push(&buffer,(routine),(srg));
#define pthread_cleanup_pop\
_pthread_clean_pop(&buffer,(exeute));
}
等待一个线程结束的函数:pthread_join
头文件:#include<pthread.h>
函数原型:int pthread_join(pthread_t th,void *thread_return);
3.私用数据
创建一个键:pthread_key_create
头文件:#include<pthread.h>
函数原型:int pthread_key_create(pthread_key_t *key,void(*destr_function)(void *));
为一个键设置使用数据:
头文件:#include<pthread.h>
函数原型:int pthread_setspecific(pthread_key_t key,(const void *pointer));
读取一个键的私有数据:pthread_getspecific
头文件:#include<pthread.h>
函数原型:void *pthread_getspecific(pthread_key_t key);
删除一个键:pthread_key_delete
头文件:#include<pthread.h>
函数原型:int pthread_key_delete(pthread_key_t key);
4.互斥锁
- 线程在运行过程中需要使用共享资源时,要保证该线程独占该资源,之中机制称为互斥。
- 方法是:在某线程需要访问共享资源时,就对其加锁,使用完后再释放锁。
初始化一个互斥锁:pthread_mutex_init
头文件:#include<pthread.h>
函数原型:int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
注销一个互斥锁:pthread_mutex_destory
头文件:#include<pthread.h>
函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
加锁,如果不成功,阻塞等待:pthread_mutex_lock
头文件:#include<pthread.h>
函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
解锁:pthread_mutex_unlock
头文件:#include<pthread.h>
函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
5.条件变量
- 条件变量通过允许线程阻塞和等待另一线程发送信号的方法弥补了互斥锁的不足,它常与互斥锁一起使用。
- 在使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开互斥锁并等待条件发生变化。一旦其他线程改变了条件变量,就将通知相应的条件变量唤醒一个或多个正被该条件变量阻塞的线程。被唤醒的线程将重新锁定互斥锁并测试条件是否满足。
初始化条件变量:pthread_cond_init
头文件:#include<pthread.h>
函数原型:int pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
基于条件变量阻塞,无条件等待:pthread_cond_wait
头文件:#include<pthread.h>
函数原型:int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
阻塞直到指定事件发生,计时等待:pthread_cond_timedwait
头文件:#include<pthread.h>
函数原型:int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);
解除特定线程的阻塞,存在多个等待线程时按入队顺序激活其中一个:pthread_cond_signal
头文件:#include<pthread.h>
函数原型:int pthread_cond_signal(pthread_cond_t *cond);
解除所有线程的阻塞:pthread_cond_broadcast
头文件:#include<pthread.h>
函数原型:int pthread_cond_broadcast(pthread_cond_t *cond);
清除条件变量:pthread_cond_destroy
头文件:#include<pthread.h>
函数原型:int pthread_cond_destroy(pthread_cond_t *cond);
6.异步信号
- 信号量本质上市一个非负的整数计数器,可以控制对公共资源的访问,其数据类型为sem_t,对应的头文件为semaphore.h
用来向特定线程发送信号:pthread_kill
头文件#include<pthread.h>
函数原型:int pthread_kill(pthread_t threadid,int signo);
设置线程的信号屏蔽码:pthread_sigmask(但对不允许屏蔽的Cancel信号和不允许响应的Restart信号进行了保护)
头文件#include<pthread.h>
函数原型:int pthread_sigmask(int how,const sigset_t *newmask,sigset_t *oldmask);
阻塞线程:sigwait
头文件#include<pthread.h>
函数原型:int sigwait (const sigset_t *set,int *sig);
(二)实验代码分析
这是多线程应用程序设计_著名生产者消费者问题
- 生产者和消费者问题是从操作系统中的许多实际同步问题中抽象出来的具有代表性的问题,它反映了操作系统中典型的同步例子,生产者进程(进程由多个线程组成)生产信息,消费者进程使用信息,由于生产者和消费者彼此独立,且运行速度不确定,所以很可能出现生产者已产生了信息而消费者却没有来得及接受信息这种情况。为此,需要引入由一个或者若干个存储单元组成的临时存储区(即缓冲区),以便存放生产者所产生的信息,解决平滑进程间由于速度不确定所带来的问题。
程序功能
- 程序源代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "pthread.h"
#define BUFFER_SIZE 16
/* 设置一个整数的循环缓冲区。 */
struct prodcons {
int buffer[BUFFER_SIZE]; /* 缓存区数组 */
pthread_mutex_t lock; /* 互斥锁:互斥保证缓冲区的互斥访问 */
int readpos, writepos; /* 读写的位置 */
pthread_cond_t notempty; /* 当缓冲区没有空信号*/
pthread_cond_t notfull; /* 当缓冲区没有满信号 */
};
/*--------------------------------------------------------*/
/* 初始化缓冲区 */
void init(struct prodcons * 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;
}
/*--------------------------------------------------------*/
/*在存储缓冲区中写入整数*/
void put(struct prodcons * b, int data)
{
pthread_mutex_lock(&b->lock);
/* 等待缓冲区非满 */
while ((b->writepos + 1) % BUFFER_SIZE == b->readpos) {
printf("wait for not full\n");
pthread_cond_wait(&b->notfull, &b->lock);
}
/* 写入数据,并提前写指针*/
b->buffer[b->writepos] = data;
b->writepos++; /* 缓冲区指针加1*/
if (b->writepos >= BUFFER_SIZE) b->writepos = 0;
/* 信号缓冲区此时非空 */
pthread_cond_signal(&b->notempty);/* 发缓冲区不空信号 */
pthread_mutex_unlock(&b->lock);
}
/*--------------------------------------------------------*/
/* 读取并从缓冲区中删除一个整数 */
int get(struct prodcons * b)
{
int data;
pthread_mutex_lock(&b->lock);
/* 等待缓冲非空 */
while (b->writepos == b->readpos) {
printf("wait for not empty\n");
pthread_cond_wait(&b->notempty, &b->lock);
}
/*读取数据并提前读取指针*/
data = b->buffer[b->readpos];
b->readpos++;
/*读取指针加1*/
if (b->readpos >= BUFFER_SIZE) b->readpos = 0;
/* 信号缓冲区现在非满*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
return data;
}
/*--------------------------------------------------------*/
#define OVER (-1)
struct prodcons buffer;
/*--------------------------------------------------------*/
void * producer(void * data)
{
int n;
for (n = 0; n < 1000; n++) {
printf(" put-->%d\n", n);
put(&buffer, n);
}
put(&buffer, OVER);
printf("producer stopped!\n");
return NULL;
}
/*--------------------------------------------------------*/
void * consumer(void * data)
{
int d;
while (1) {
d = get(&buffer);
if (d == OVER ) break;
printf(" %d-->get\n", d);
}
printf("consumer stopped!\n");
return NULL;
}
/*--------------------------------------------------------*/
int main(void)
{
pthread_t th_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);
return 0;
}
- 串口通信分析
fd=open("/dev/ttyS1",O_NOCTTY|O_RDWR|O_NONBLOCK);
if( fd < 0)
{
perror("Unable open /dev/ttyS0\r ");
return 1;
}
- 这是文件I/O的常用函数,open函数,open函数用来打开一个设备,他返回的是一个整型变量,如果这个值等于-1,说明打开文件出现错误,如果为大于0的值,那么这个值代表的就是文件描述符。
- 这个事常用的一种用法fd是设备描述符,linux在操作硬件设备时,屏蔽了硬件的基本细节,只把硬件当做文件来进行操作,而所有的操作都是以open函数来开始,它用来获取fd,然后后期的其他操作全部控制fd来完成对硬件设备的实际操作。
O_RDWR 读写方式打开;
O_NOCTTY 不允许进程管理串口(不太理解,一般都选上);
O_NDELAY 非阻塞(默认为阻塞,打开后也可以使用fcntl()重新设置)
- 常用串口操作
写入:n = write(fd, "linux", 5);
n实际写入字节数;
读取:res = read(fd,buf,len);
res 读取的字节数;
设置:fcntl(fd, F_SETFL, FNDELAY); //非阻塞
fcntl(fd, F_SETFL, 0); // 阻塞
关闭:close(fd);
- 串口设置
struct termios options; // 串口配置结构体
tcgetattr(fd,&options); //获取当前设置
bzero(&options,sizeof(options));
options.c_cflag |= B115200 | CLOCAL | CREAD; // 设置波特率,本地连接,接收使能
options.c_cflag &= ~CSIZE; //屏蔽数据位
options.c_cflag |= CS8; // 数据位为 8 ,CS7 for 7
options.c_cflag &= ~CSTOPB; // 一位停止位, 两位停止为 |= CSTOPB
options.c_cflag &= ~PARENB; // 无校验
//options.c_cflag |= PARENB; //有校验
//options.c_cflag &= ~PARODD // 偶校验
//options.c_cflag |= PARODD // 奇校验
options.c_cc[VTIME] = 0; // 等待时间,单位百毫秒 (读)。后有详细说明
options.c_cc[VMIN] = 0; // 最小字节数 (读)。后有详细说明
tcflush(fd, TCIOFLUSH); // TCIFLUSH刷清输入队列。
TCOFLUSH刷清输出队列。
TCIOFLUSH刷清输入、输出队列。
tcsetattr(fd, TCSANOW, &options); // TCSANOW立即生效;
TCSADRAIN:Wait until everything has been transmitted;
TCSAFLUSH:Flush input and output buffers and make the change
1.打开串口函数open_port()中要实现的函数:
(1)open("/dev/ttys0",O_RDWR | O_NOCTTY | O_NDELAY);/*打开串口0*/
(2)fcntl(fd,F_SETFL,0)/*恢复串口为阻塞状态*/
(3)isatty(STDIN_FILENO) /*测试是否为中断设备 非0即是中断设备*/
2.配置串口参数函数set_opt()中要实现的函数:
(1)保存原先有串口配置
tcgetattr(fd,&oldtio);
(2)先将新串口配置清0
bzore(&newtio,sizeof(newito));
(3)激活选项CLOCAL和CREAD 并设置数据位大小
newtio.c_cflag |=CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |=CS8;
(4)设置奇偶校验
奇校验:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
偶校验:
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PAREND;
newtio.c_cflag &= ~PARODD;
无奇偶校验:
newtio.c_cflag &= ~PARENB;
(5) 设置停止位
newtio.c_cflag &= ~CSTOPB; /*停止位为1*/
newtio.c_cflag |= CSTOPB;/*停止位为0*/
(6)设置波特率:
cfsetispeed(&newtio,B115200);
cfsetospeed(&newtio,B115200);
(7)设置等待时间和最小接受字符:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
(8)处理为接收字符:
tcflush(fd,TCIFLUSH);
(9)激活新配置:
tcsetattr(fd,TCSANOW,&newtio);
3.读写串口
write(fd,buff,8);
read(fd,buff,8);
实验体会
本次实验是固件开发,从实验的操作流程上看,这次实验与实验一的步骤基本相同,唯一的不同就是编译.c文件时的命令加了参数-lpthread,这是因为本次实验的两个代码是与多线程相关的代码,编译时需要加-lpthread 的库。由于做完实验一接着就做了实验二,所以还是比较顺利的。实验二的代码较复杂,其中所包含的知识点也有很多,需要花时间去理解和体会。