|
6.6 实验内容
6.6.1 文件读写及上锁
1.实验目的
通过编写文件读写及上锁的程序,进一步熟悉Linux中文件I/O相关的应用开发,并且熟练掌握open()、read()、write()、fcntl()等函数的使用。
2.实验内容
在Linux中FIFO是一种进程之间的管道通信机制。Linux支持完整的FIFO通信机制。
本实验内容比较有趣,通过使用文件操作,仿真FIFO(先进先出)结构以及生产者-消费者运行模型。
本实验中需要打开两个虚拟终端,分别运行生产者程序(producer)和消费者程序(customer)。此时两个进程同时对同一个文件进行读写操作。因为这个文件是临界资源,所以可以使用文件锁机制来保证两个进程对文件的访问都是原子操作。
先启动生产者进程,它负责创建仿真FIFO结构的文件(其实是一个普通文件)并投入生产,就是按照给定的时间间隔,向FIFO文件写入自动生成的字符(在程序中用宏定义选择使用数字还是使用英文字符),生产周期以及要生产的资源数通过参数传递给进程(默认生产周期为1s,要生产的资源数为10个字符)。
后启动的消费者进程按照给定的数目进行消费,首先从文件中读取相应数目的字符并在屏幕上显示,然后从文件中删除刚才消费过的数据。为了仿真FIFO结构,此时需要使用两次复制来实现文件内容的偏移。每次消费的资源数通过参数传递给进程,默认值为10个字符。
3.实验步骤
(1)画出实验流程图。
本实验的两个程序的流程图如图6.4所示。
图6.4 节流程图
(2)编写代码。
本实验中的生产者程序的源代码如下所示,其中用到的lock_set()函数可参见第6.3.2节。
/* producer.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "mylock.h"
#define MAXLEN 10 /* 缓冲区大小最大值*/
#define ALPHABET 1 /* 表示使用英文字符 */
#define ALPHABET_START 'a' /* 头一个字符,可以用 'A'*/
#define COUNT_OF_ALPHABET 26 /* 字母字符的个数 */
#define DIGIT 2 /* 表示使用数字字符 */
#define DIGIT_START '0' /* 头一个字符 */
#define COUNT_OF_DIGIT 10 /* 数字字符的个数 */
#define SIGN_TYPE ALPHABET /* 本实例选用英文字符 */
const char *fifo_file = "./myfifo"; /* 仿真FIFO文件名 */
char buff[MAXLEN]; /* 缓冲区 */
/* 功能:生产一个字符并写入仿真FIFO文件中 */
int product(void)
{
int fd;
unsigned int sign_type, sign_start, sign_count, size;
static unsigned int counter = 0;
/* 打开仿真FIFO文件 */
if ((fd = open(fifo_file, O_CREAT|O_RDWR|O_APPEND, 0644)) < 0)
{
printf("Open fifo file error\n");
exit(1);
}
sign_type = SIGN_TYPE;
switch(sign_type)
{
case ALPHABET:/* 英文字符 */
{
sign_start = ALPHABET_START;
sign_count = COUNT_OF_ALPHABET;
}
break;
case DIGIT:/* 数字字符 */
{
sign_start = DIGIT_START;
sign_count = COUNT_OF_DIGIT;
}
break;
default:
{
return -1;
}
}/*end of switch*/
sprintf(buff, "%c", (sign_start + counter));
counter = (counter + 1) % sign_count;
lock_set(fd, F_WRLCK); /* 上写锁*/
if ((size = write(fd, buff, strlen(buff))) < 0)
{
printf("Producer: write error\n");
return -1;
}
lock_set(fd, F_UNLCK); /* 解锁 */
close(fd);
return 0;
}
int main(int argc ,char *argv[])
{
int time_step = 1; /* 生产周期 */
int time_life = 10; /* 需要生产的资源数 */
if (argc > 1)
{/* 第一个参数表示生产周期 */
sscanf(argv[1], "%d", &time_step);
}
if (argc > 2)
{/* 第二个参数表示需要生产的资源数 */
sscanf(argv[2], "%d", &time_life);
}
while (time_life--)
{
if (product() < 0)
{
break;
}
sleep(time_step);
}
exit(EXIT_SUCCESS);
}
本实验中的消费者程序的源代码如下所示。
/* customer.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define MAX_FILE_SIZE 100 * 1024 * 1024 /* 100M*/
const char *fifo_file = "./myfifo"; /* 仿真FIFO文件名 */
const char *tmp_file = "./tmp"; /* 临时文件名 */
/* 资源消费函数 */
int customing(const char *myfifo, int need)
{
int fd;
char buff;
int counter = 0;
if ((fd = open(myfifo, O_RDONLY)) < 0)
{
printf("Function customing error\n");
return -1;
}
printf("Enjoy:");
lseek(fd, SEEK_SET, 0);
while (counter < need)
{
while ((read(fd, &buff, 1) == 1) && (counter < need))
{
fputc(buff, stdout); /* 消费就是在屏幕上简单的显示 */
counter++;
}
fputs("\n", stdout);
close(fd);
return 0;
}
/* 功能:从sour_file文件的offset偏移处开始
将count个字节数据复制到dest_file文件 */
int myfilecopy(const char *sour_file,
const char *dest_file, int offset, int count, int copy_mode)
{
int in_file, out_file;
int counter = 0;
char buff_unit;
if ((in_file = open(sour_file, O_RDONLY|O_NONBLOCK)) < 0)
{
printf("Function myfilecopy error in source file\n");
return -1;
}
if ((out_file = open(dest_file,
O_CREAT|O_RDWR|O_TRUNC|O_NONBLOCK, 0644)) < 0)
{
printf("Function myfilecopy error in destination file:");
return -1;
}
lseek(in_file, offset, SEEK_SET);
while ((read(in_file, &buff_unit, 1) == 1) && (counter < count))
{
write(out_file, &buff_unit, 1);
counter++;
}
close(in_file);
close(out_file);
return 0;
}
/* 功能:实现FIFO消费者 */
int custom(int need)
{
int fd;
/* 对资源进行消费,need表示该消费的资源数目 */
customing(fifo_file, need);
if ((fd = open(fifo_file, O_RDWR)) < 0)
{
printf("Function myfilecopy error in source_file:");
return -1;
}
/* 为了模拟FIFO结构,对整个文件内容进行平行移动 */
lock_set(fd, F_WRLCK);
myfilecopy(fifo_file, tmp_file, need, MAX_FILE_SIZE, 0);
myfilecopy(tmp_file, fifo_file, 0, MAX_FILE_SIZE, 0);
lock_set(fd, F_UNLCK);
unlink(tmp_file);
close(fd);
return 0;
}
int main(int argc ,char *argv[])
{
int customer_capacity = 10;
if (argc > 1) /* 第一个参数指定需要消费的资源数目,默认值为10 */
{
sscanf(argv[1], "%d", &customer_capacity);
}
if (customer_capacity > 0)
{
custom(customer_capacity);
}
exit(EXIT_SUCCESS);
}
(3)先在宿主机上编译该程序,如下所示:
$ make clean; make
(4)在确保没有编译错误后,交叉编译该程序,此时需要修改Makefile中的变量
CC = arm-linux-gcc /* 修改Makefile中的编译器 */
$ make clean; make
(5)将生成的可执行程序下载到目标板上运行。
4.实验结果
此实验在目标板上的运行结果如下所示。实验结果会和这两个进程运行的具体过程相关,希望读者能具体分析每种情况。下面列出其中一种情况:
终端一:
$ ./producer 1 20 /* 生产周期为1s,需要生产的资源数为20个 */
Write lock set by 21867
Release lock by 21867
Write lock set by 21867
Release lock by 21867
……
终端二:
$ ./customer 5 /* 需要消费的资源数为5个 */
Enjoy:abcde /* 消费资源,即打印到屏幕上 */
Write lock set by 21872 /* 为了仿真FIFO结构,进行两次复制 */
Release lock by 21872
在两个进程结束之后,仿真FIFO文件的内容如下:
$ cat myfifo
fghijklmnopqr /* a~e的5个字符已经被消费,就剩下后面15个字符 */
6.6.2 多路复用式串口操作
1.实验目的
通过编写多路复用式串口读写,进一步理解多路复用函数的用法,同时更加熟练掌握Linux设备文件的读写方法。
2.实验内容
本实验主要实现两台机器(宿主机和目标板)之间的串口通信,每台机器都可以发送和接收数据。除了串口设备名称不同(宿主机上使用串口1:/dev/ttyS0,而在目标板上使用串口2:/dev/ttyS1),两台机器上的程序基本相同。
3.实验步骤
(1)画出流程图
如图6.5所示为程序流程图,两台机器上的程序使用同样的流程图。
图6.5 宿主机/目标板程序的流程图
(2)编写代码。
编写宿主机和目标板上的代码,在这些程序中用到的open_port()和set_com_config()函数请参照6.4节。这里只列出宿主机上的代码。
/* com_host.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "uart_api.h"
int main(void)
{
int fds[SEL_FILE_NUM], recv_fd, maxfd;
char buff[BUFFER_SIZE];
fd_set inset,tmp_inset;
struct timeval tv;
unsigned loop = 1;
int res, real_read, i;
/* 将从串口读取的数据写入这个文件中 */
if ((recv_fd = open(RECV_FILE_NAME, O_CREAT|O_WRONLY, 0644)) < 0)
{
perror("open");
return 1;
}
fds[0] = STDIN_FILENO; /* 标准输入 */
if ((fds[1] = open_port(HOST_COM_PORT)) < 0) /* 打开串口 */
{
perror("open_port");
return 1;
}
if (set_com_config(fds[1], 115200, 8, 'N', 1) < 0) /* 配置串口 */
{
perror("set_com_config");
return 1;
}
FD_ZERO(&inset);
FD_SET(fds[0], &inset);
FD_SET(fds[1], &inset);
maxfd = (fds[0] > fds[1])?fds[0]:fds[1];
tv.tv_sec = TIME_DELAY;
tv.tv_usec = 0;
printf("Input some words(enter 'quit' to exit):\n");
while (loop && (FD_ISSET(fds[0], &inset) || FD_ISSET(fds[1], &inset)))
{
tmp_inset = inset;
res = select(maxfd + 1, &tmp_inset, NULL, NULL, &tv);
switch(res)
{
case -1:
{
perror("select");
loop = 0;
}
break;
case 0: /* Timeout */
{
perror("select time out");
loop = 0;
}
break;
default:
{
for (i = 0; i < SEL_FILE_NUM; i++)
{
if (FD_ISSET(fds, &tmp_inset))
{
memset(buff, 0, BUFFER_SIZE);
/* 读取标准输入或者串口设备文件 */
real_read = read(fds, buff, BUFFER_SIZE);
if ((real_read < 0) && (errno != EAGAIN))
{
loop = 0;
}
else if (!real_read)
{
close(fds);
FD_CLR(fds, &inset);
}
else
{
buff[real_read] = '\0';
if (i == 0)
{ /* 将从终端读取的数据写入串口*/
write(fds[1], buff, strlen(buff));
printf("Input some words
(enter 'quit' to exit):\n");
}
else if (i == 1)
{ /* 将从串口读取的数据写入普通文件中*/
write(recv_fd, buff, real_read);
}
if (strncmp(buff, "quit", 4) == 0)
{ /* 如果读取为‘quit’则退出*/
loop = 0;
}
}
} /* end of if FD_ISSET */
} /* for i */
}
} /* end of switch */
} /* end of while */
close(recv_fd);
return 0;
}
(3)接下来,将目标板的串口程序交叉编译,再将宿主机的串口程序在PC机上编译。
(4)连接PC的串口1和开发板的串口2。然后将目标板串口程序下载到开发板上,分别在两台机器上运行串口程序。
4.实验结果
宿主机上的运行结果如下所示:
$ ./com_host
Input some words(enter 'quit' to exit):
Hello, Target!
Input some words(enter 'quit' to exit):
I'm host program!
Input some words(enter 'quit' to exit):
Byebye!
Input some words(enter 'quit' to exit):
quit /* 这个输入使双方的程序都结束*/
从串口读取的数据(即目标板中发送过来的数据)写入同目录下的recv.dat文件中。
$ cat recv.dat
Hello, Host!
I'm target program!
Byebye!
目标板上的运行结果如下所示:
$ ./com_target
Input some words(enter 'quit' to exit):
Hello, Host!
Input some words(enter 'quit' to exit):
I'm target program!
Input some words(enter 'quit' to exit):
Byebye!
与宿主机上的代码相同,从串口读取的数据(即目标板中发送过来的数据)写入同目录下的recv.dat文件中。
$ cat recv.dat
Hello, Target!
I'm host program!
Byebye!
Quit
请读者用poll()函数实现具有以上功能的代码。 |
|