20145312 《信息安全系统设计基础》实验二 固件设计
20145312 《信息安全系统设计基础》实验二 固件设计
一.实验目的
- 掌握程序的调试方法
- 实现密码学中常见算法的固化
二.实验内容
- 选择常见的分组算法或非对称算法实现,并在ARM开发环境中调试
- 读懂本实验程序源代码(pthread.c),学习终端I/O 函数的使用方法,学习将多线程编程应用到串口的接收和发送程序设计中
- 进入/arm2410cl/exp/basic/02_pthread 目录,运行make 产生pthread 程序,使用NFS方式连接开发主机进行运行实验
三.实验步骤
1. 搭建实验平台,同实验一(实验一平台搭建后,继续使用)
- 连接arm开发板
- 建立超级终端
- 启动实验平台(redhat虚拟机)
- 配置同网段IP
- 安装arm编译器(bc共享文件夹)
- 配置环境变量(redhat虚拟机中)
2. 导入实验代码
- 将实验所需代码拷贝到bc共享文件夹中 (实验代码在老师提供的02_pthread和03_tty文件夹中)
3. 在虚拟机中编译代码
- 输入命令:
armv4l-unknown-linux-gcc pthread.c -o pthread -lpthread
- 对于多线程相关的代码,编译时需要加-lpthread 的库。
- 在虚拟机中编译term.c
4. 下载调试
- 在超级终端中运行可执行文件pthread:
- 在超级终端中运行可执行文件term。
运行结果如图:
代码分析与过程理解
1. 多线程应用程序设计(pthread.c)
重要的函数做说明
- 线程创建函数:
int pthread_create (pthread_t * thread_id, __const pthread_attr_t * __attr,
void *(*__start_routine) (void *),void *__restrict __arg)
说明: 线程创建函数第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,函数 thread 不需要参数,所以最后一个参数设为空指针。第二个参数也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回 0,若不为 0 则说明创建线程失败,常见的错误返回代码为 EAGAIN 和 EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
- 获得父进程 ID:
pthread_t pthread_self (void)
- 测试两个线程号是否相同:
int pthread_equal (pthread_t __thread1, pthread_t __thread2)
- 线程退出:
一个线程的结束有两种途径,一种是函数结束了,调用它的线程也就结束了;另一种方式是通过函数 pthread_exit 来实现。它的函数原型为:
void pthread_exit (void *__retval)
说明: 唯一的参数是函数的返回代码,只要 pthread_join 中的第二个参数 thread_return 不是 NULL,这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用 pthread_join 的线程则返回错误代码 ESRCH。
- 等待指定的线程结束:
int pthread_join (pthread_t __th, void **__thread_return)
说明: 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
- 互斥量初始化:
pthread_mutex_init (pthread_mutex_t *,__const pthread_mutexattr_t *)
- 销毁互斥量:
int pthread_mutex_destroy (pthread_mutex_t *__mutex)
- 再试一次获得对互斥量的锁定(非阻塞):
int pthread_mutex_trylock (pthread_mutex_t *__mutex)
- 锁定互斥量(阻塞):
int pthread_mutex_lock (pthread_mutex_t *__mutex)
- 解锁互斥量:
int pthread_mutex_unlock (pthread_mutex_t *__mutex)
- 条件变量初始化:
int pthread_cond_init (pthread_cond_t *__restrict __cond,
__const pthread_condattr_t *__restrict __cond_attr)
说明: 其中 cond 是一个指向结构 pthread_cond_t 的指针,cond_attr 是一个指向结构pthread_condattr_t 的指针。结构 pthread_condattr_t 是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是 PTHREAD_PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为 pthread_cond_ destroy(pthread_cond_t cond)。
- 销毁条件变量 COND:
int pthread_cond_destroy (pthread_cond_t *__cond)
- 唤醒线程等待条件变量:
int pthread_cond_signal (pthread_cond_t *__cond)
说明: 它用来释放被阻塞在条件变量 cond 上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用 pthread_cond_wait 函数之间被发出,从而造成无限制的等待。
- 等待条件变量(阻塞):
int pthread_cond_wait (pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex)
说明: 线程解开mutex指向的锁并被条件变量 cond 阻 塞 。 线程可以被函数pthread_cond_signal 和函数 pthread_cond_broadcast 唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为 0 等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用 while 语句实现。
- 在指定的时间到达前等待条件变量:
int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex, __const struct timespec *__restrict __abstime)
说明: 它比函数 pthread_cond_wait()多了一个时间参数,经历 abstime 段时间后,即使条件变量不满足,阻塞也被解除。
程序源代码
#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;
}
2. 串行端口程序设计(term.c)
程序的主要部分分析
- 头文件
#include <stdio.h> /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*linux 标准函数定义*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> /*文件控制定义*/
#include <termios.h> /*PPSIX 终端控制定义*/
#include <errno.h> /*错误号定义*/
#include <pthread.h> /*线程库定义*/
打开串口
在 Linux 下串口文件位于/dev 下,一般在老版本的内核中串口一为/dev/ttyS0 ,串口二为 /dev/ttyS1, 在我们的开发板中串口设备位于/dev/tts/下,因为开发板中没有 ttyS0这个设备,所以我们要建立一个连接,方法如下:
[/mnt/yaffs] cd /dev
[/dev] ln –sf /dev/tts/0 ttyS0
打开串口是通过标准的文件打开函数来实现的
int fd;
fd = open( "/dev/ttyS0", O_RDWR); /*以读写方式打开串口*/
if (-1 == fd){ /* 不能打开串口一*/
perror(" 提示错误!");
}
串口设置
最基本的设置串口包括波特率设置,效验位和停止位设置。串口的设置主要是设置struct termios 结构体的各成员值,关于该结构体的定义可以查看/arm2410cl/kernel/linux-2.4.18-2410cl/include/asm/termios.h
文件。
struct termio
{
unsigned short c_iflag; /* 输入模式标志 */
unsigned short c_oflag; /* 输出模式标志 */
unsigned short c_cflag; /* 控制模式标志 */
unsigned short c_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned char c_cc[NCC]; /* control characters */
};
- 波特率设置:
下面是修改波特率的代码:
struct termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200); /*设置为 19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);
- 校验位和停止位的设置:
无效验 8 位
Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS8;
- 奇效验(Odd) 7 位
Option.c_cflag |= ~PARENB;
Option.c_cflag &= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
- 偶效验(Even) 7 位
Option.c_cflag &= ~PARENB;
Option.c_cflag |= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
- Space 效验 7 位
Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= &~CSIZE;
Option.c_cflag |= CS8;
- z 设置停止位:
1 位:
options.c_cflag &= ~CSTOPB;
2 位:
options.c_cflag |= CSTOPB;
读写串口
设置好串口之后,读写串口就很容易了,把串口当作文件读写就可以了。
- 发送数据:
char buffer[1024];
int Length=1024;
int nByte;
nByte = write(fd, buffer ,Length)
- 读取串口数据:
使用文件操作 read 函数读取,如果设置为原始模式(Raw Mode)传输数据,那么 read函数返回的字符数是实际串口收到的字符数。可以使用操作文件的函数来实现异步读取,如 fcntl,或者 select 等来操作。
char buff[1024];
int Len=1024;
int readByte = read(fd, buff, Len);
关闭串口
关闭串口就是关闭文件。
程序源代码
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <pthread.h>
#define BAUDRATE B115200
#define COM1 "/dev/ttyS0"
#define COM2 "/dev/ttyS1"
#define ENDMINITERM 27 /* ESC to quit miniterm */
#define FALSE 0
#define TRUE 1
volatile int STOP=FALSE;
volatile int fd;
void child_handler(int s)
{
printf("stop!!!\n");
STOP=TRUE;
}
/*--------------------------------------------------------*/
void* keyboard(void * data)
{
int c;
for (;;){
c=getchar();
if( c== ENDMINITERM){
STOP=TRUE;
break ;
}
}
return NULL;
}
/*--------------------------------------------------------*/
/* modem input handler */
void* receive(void * data)
{
int c;
printf("read modem\n");
while (STOP==FALSE)
{
read(fd,&c,1); /* com port */
write(1,&c,1); /* stdout */
}
printf("exit from reading modem\n");
return NULL;
}
/*--------------------------------------------------------*/
void* send(void * data)
{
int c='0';
printf("send data\n");
while (STOP==FALSE) /* modem input handler */
{
c++;
c %= 255;
write(fd,&c,1); /* stdout */
usleep(100000);
}
return NULL; }
/*--------------------------------------------------------*/
int main(int argc,char** argv)
{
struct termios oldtio,newtio,oldstdtio,newstdtio;
struct sigaction sa;
int ok;
pthread_t th_a, th_b, th_c;
void * retval;
if( argc > 1)
fd = open(COM2, O_RDWR );
else
fd = open(COM1, O_RDWR ); //| O_NOCTTY |O_NONBLOCK);
if (fd <0) {
error(COM1);
exit(-1);
}
tcgetattr(0,&oldstdtio);
tcgetattr(fd,&oldtio); /* save current modem settings */
tcgetattr(fd,&newstdtio); /* get working stdtio */
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; /*ctrol flag*/
newtio.c_iflag = IGNPAR; /*input flag*/
newtio.c_oflag = 0; /*output flag*/
newtio.c_lflag = 0;
newtio.c_cc[VMIN]=1;
newtio.c_cc[VTIME]=0;
/* now clean the modem line and activate the settings for modem */
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio); /*set attrib*/
sa.sa_handler = child_handler;
sa.sa_flags = 0;
sigaction(SIGCHLD,&sa,NULL); /* handle dying child */
pthread_create(&th_a, NULL, keyboard, 0);
pthread_create(&th_b, NULL, receive, 0);
pthread_create(&th_c, NULL, send, 0);
pthread_join(th_a, &retval);
pthread_join(th_b, &retval);
pthread_join(th_c, &retval);
tcsetattr(fd,TCSANOW,&oldtio); /* restore old modem setings */
tcsetattr(0,TCSANOW,&oldstdtio); /* restore old tty setings */
close(fd);
exit(0);
}
四.实验中遇到的问题及解决方法
问题:
- make无法使用
解决:
-
这个是由于Makefile中环境变量的设置和在本机中的不同导致的,两个解决方法:
一是把其中的环境变量修改一下,这个是在别人的帮助下完成的; 二是直接手动编译即可。我们选择的是直接手动编译。
问题:
- 在超级终端窗口,进入03_tty所在目录,运行term时,出现错误
/dev/ttyS0:No such file or directory
解决:
- 这个按照指导书的方法解决了:
[/mnt/yaffs]cd /dev
[/dev] ln –sf /dev/tts/0 ttyS0 (注意字母大小写,数字为0或1)
- 这是因为在 Linux 下串口文件位于/dev 下,一般在老版本的内核中串口一为/dev/ttyS0 ,串口二为 /dev/ttyS1, 而在开发板中串口设备位于/dev/tts/下,因为开发板中没有ttyS0这个设备,所以我们要建立一个连接。
五.实验体会
本次试验了解多线程程序设计的基本原理,学习 pthread 库函数的使用,了解在 linux 环境下串行程序设计的基本方法,掌握终端的主要属性及设置方法,熟悉终端I /O 函数的使用,学习使用多线程来完成串口的收发处理。学到了很多新的知识概念,同时也增加了许多需要学习的未知事物。实验平台搭建直接沿用实验一的平台,没有出现问题什么难的问题