资源描述
实验四:Linux进程及文件操作程序设计
第一部分 Linux下文件控制程序设计
一、实验目的
1. 熟悉Linux下文件控制程序设计方法。
2. 掌握利用文件锁实现文件互斥访问程序设计方法。
二、实验设备
装有Red Hat Enterprise Linux 操作系统的PC机
三、实验内容
1、预备知识
当多个用户共同使用、操作一个文件时,Linux 通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。文件锁包括建议性锁和强制性锁。建议性锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁。强制性锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
在Linux 中,实现文件上锁的函数有flock和fcntl,其中flock用于对文件施加建议性锁,而fcntl不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁。
记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。当然,在文件的同一部分不能同时建立读取锁和写入锁。
2、fcntl()函数格式
用于建立记录锁的fcntl函数格式如表7.1所示。
表7.1 fcntl函数语法要点
所需头文件
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
函数原型
int fcnt1(int fd,int cmd,struct flock *lock)
函数传入参数
fd
文件描述符
cmd(命令)
F_DUPFD:复制文件描述符
F_GETFL:得到open设置的标志
F_SETFL:改变open设置的标志
F_SETLK:设置lock描述的文件锁
F_SETLKW:这是F_SETLK的阻塞版本
F_GETLK:获取上锁状态。
lock
设置锁参数
函数返回值
成功:0 出错:1
这里,lock的结构如下所示:
struct flock
{
short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}
lock结构中每个变量的取值含义如表7.2所示。
表7.2 lock结构变量取值
l_type
F_RDLCK:读取锁(共享锁)
F_WRLCK:写入锁(排斥锁)
F_UNLCK:解锁
l_stat
相对位移量(字节)
l_whence:相对位移量的起点
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小。
SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量。
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。
l_len
加锁区域的长度。
说明
为加锁整个文件,通常的方法是将l_start =0,l_whence=SEEK_SET,l_len=0。
3、实验内容
编程实现使用fcntl 函数为文件上锁的程序。首先创建了一个hello文件,之后对其上写入锁,最后释放写入锁。
编程步骤如下:
1)首先对flock结构体的对应位赋予相应的值。
2)接着调用fcntl函数判断文件是否可以上锁,如果可以,则对其上写入锁(F_SETLKW);如果已有锁存在,则打印锁类型及上锁进程号,并为其上阻塞性写文件锁(F_SETLKW)。
四、实验步骤
1、新建一个子目录。
cd /home
mkdir exp5
cd exp5
2、编写实现上述功能的程序代码
编辑上锁子函数:vi lock_set.c 启动vi,然后,按i进入插入状态,并输入源程序。最后,按ESC返回到底行模式,并按:wq存盘退出。
编辑写锁测试主程序:vi write_lock.c启动vi,然后,按i进入插入状态,并输入源程序。最后,按ESC返回到底行模式,并按:wq存盘退出。。
3、编译、调试上述程序。
gcc write_lock.c –o write_lock
4、在PC机端开启两个终端,并且在两个终端上同时运行该程序,以达到多个进程操作一个文件的效果。
新建第一个终端:桌面点右键选择“新建一个终端”
运行write_lock:./ write_lock (第一个终端对hello文件加写锁)
新建第二个终端:桌面点右键选择“新建一个终端”
运行write_lock:./ write_lock (第二个终端对hello文件加写锁,由于第一个写锁还未退出,所以该写锁被阻塞)
在第一个终端按任意键,进行文件解锁。
观察第二个终端,加锁成功。
5、观察实验结果。
首先在终端一上运行./write_lock,然后新建另一个终端并运行./write_lock,由于第一个终端上已经给文件hello上锁了,所以第二个终端程序不能对文件hello上锁而被阻塞。
在第一个终端上,按任意键,使其释放写文件锁。此时,观察终端二可以发现,终端二上的加锁程序完成了加锁任务。
五、实验程序清单
1、实现文件加锁的子程序
/* lock_set.c */
int lock_set(int fd, int type)
{
struct flock old_lock, lock;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = type;
lock.l_pid = -1;
/* 判断文件是否可以上锁 */
fcntl(fd, F_GETLK, &lock);
if (lock.l_type != F_UNLCK)
{
/* 判断文件不能上锁的原因 */
if (lock.l_type == F_RDLCK) /* 该文件已有读取锁 */
{
printf("Read lock already set by %d\n", lock.l_pid);
}
else if (lock.l_type == F_WRLCK) /* 该文件已有写入锁 */
{
printf("Write lock already set by %d\n", lock.l_pid);
}
}
/* l_type 可能已被F_GETLK修改过 */
lock.l_type = type;
/* 根据不同的type值进行阻塞式上锁或解锁 */
if ((fcntl(fd, F_SETLKW, &lock)) < 0)
{
printf("Lock failed:type = %d\n", lock.l_type);
return 1;
}
switch(lock.l_type)
{
case F_RDLCK:
{
printf("Read lock set by %d\n", getpid());
}
break;
case F_WRLCK:
{
printf("Write lock set by %d\n", getpid());
}
break;
case F_UNLCK:
{
printf("Release lock by %d\n", getpid());
return 1;
}
break;
default:
break;
}/* end of switch */
return 0;
}
2、测试用主程序
/* write_lock.c */
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include "lock_set.c"
int main(void)
{
int fd;
/* 首先打开文件 */
fd = open("hello", O_RDWR | O_CREAT, 0644);
if(fd < 0)
{
printf("Open file error\n");
exit(1);
}
/* 给文件上写入锁 */
lock_set(fd, F_WRLCK);
getchar();
/* 给文件解锁 */
lock_set(fd, F_UNLCK);
getchar();
close(fd);
return 0;
}
第二部分: Linux下的多线程程序设计
一、实验目的
1、 通过编写经典的“生产者消费者”问题,进一步熟悉Linux 中多线程编程
2、 掌握用信号量处理线程间的同步互斥的方法。
二、实验设备
装有Red Hat Enterprise Linux 操作系统的PC机
三、实验内容
“生产者消费者”问题描述如下:
有一个有限缓冲区和两个线程:生产者和消费者。他们分别把产品放入缓冲区和从缓冲区中拿走产品。生产者在缓冲区满时必须等待,消费者在缓冲区空时必须等待。同时,要求生产者和消费者不能同时访问缓冲区。它们之间的关系如图1所示:
图1 生产者消费者问题描述
这里要求用有名管道来模拟有限缓冲区,用信号量来解决生产者消费者问题中的同步和互斥问题。
1、信号量的考虑
可以使用3个信号量,其中两个信号量avail和full分别用于解决生产者和消费者线程之间的同步问题,mutex是用于这两个线程之间的互斥问题。其中avail初始化为N(有界缓冲区的空单元数),mutex 初始化为1,full初始化为0。
2、流程图
“生产者消费者”问题的流程流程图如图2所示。
3、编写代码
实验代码中采用的有界缓冲区拥有3个单元,每个单元为5个字节。为了尽量体现每个信号量的意义,在程序中生产过程和消费过程随机(采取0-5s的随机时间间隔)进行的,而且生产者的速度比消费者的速度平均快两倍左右。生产者一次生产一个产品(向缓冲区中放入“hello”字符串),消费者一次消费一个产品。
四、实验步骤
1、新建目录存放所设计的程序。
cd /home
mkdir exp8
cd exp8
2、编写“生产者消费者”问题的程序。
输入命令 vi producer-customer.c 启动vi,然后,按i进入插入状态,并输入源程序。最后,按ESC返回到底行模式,并按:wq存盘退出。
3、编译、调试上述程序。
gcc producer-customer.c –o producer-customer –lpthread
4、新建一个终端:桌面点右键选择“新建一个终端”
运行 ./producer-customer
5、观察实验结果。
图2. “生产者消费者”问题的流程流程图
五、参考程序清单
/*producer-customer.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MYFIFO "myfifo" /* 缓冲区有名管道的名字 */
#define BUFFER_SIZE 3 /* 缓冲区的单元数 */
#define UNIT_SIZE 5 /* 每个单元的大小 */
#define RUN_TIME 30 /* 运行时间 */
#define DELAY_TIME_LEVELS 5.0 /* 周期的最大值 */
int fd;
time_t end_time;
sem_t mutex, full, avail; /* 三个信号量 */
/*生产者线程*/
void *producer(void *arg)
{
int real_write;
int delay_time = 0;
while(time(NULL) < end_time)
{
delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX) / 2.0) + 1;
sleep(delay_time);
/*P操作信号量avail和mutex*/
sem_wait(&avail);
sem_wait(&mutex);
printf("\nProducer: delay = %d\n", delay_time);
/*生产者写入数据*/
if ((real_write = write(fd, "hello", UNIT_SIZE)) == -1)
{
if(errno == EAGAIN)
{
printf("The FIFO has not been read yet.Please try later\n");
}
}
else
{
printf("Write %d to the FIFO\n", real_write);
}
/*V操作信号量full和mutex*/
sem_post(&full);
sem_post(&mutex);
}
pthread_exit(NULL);
}
/* 消费者线程*/
void *customer(void *arg)
{
unsigned char read_buffer[UNIT_SIZE];
int real_read;
int delay_time;
while(time(NULL) < end_time)
{
delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
sleep(delay_time);
/*P操作信号量full和mutex*/
sem_wait(&full);
sem_wait(&mutex);
memset(read_buffer, 0, UNIT_SIZE);
printf("\nCustomer: delay = %d\n", delay_time);
if ((real_read = read(fd, read_buffer, UNIT_SIZE)) == -1)
{
if (errno == EAGAIN)
{
printf("No data yet\n");
}
}
printf("Read %s from FIFO\n", read_buffer);
/*V操作信号量avail和mutex*/
sem_post(&avail);
sem_post(&mutex);
}
pthread_exit(NULL);
}
int main()
{
pthread_t thrd_prd_id,thrd_cst_id;
pthread_t mon_th_id;
int ret;
srand(time(NULL));
end_time = time(NULL) + RUN_TIME;
/*创建有名管道*/
if((mkfifo(MYFIFO, 0777)<0)&&(errno!=EEXIST))
{
printf("Cannot create fifo\n");
return errno;
}
/*打开管道*/
fd = open(MYFIFO, O_RDWR);
if (fd == -1)
{
printf("Open fifo error\n");
return fd;
}
/*初始化互斥信号量为1*/
ret = sem_init(&mutex, 0, 1);
/*初始化avail信号量为N*/
ret += sem_init(&avail, 0, BUFFER_SIZE);
/*初始化full信号量为0*/
ret += sem_init(&full, 0, 0);
if (ret != 0)
{
printf("Any semaphore initialization failed\n");
return ret;
}
/*创建两个线程*/
ret = pthread_create(&thrd_prd_id, NULL, producer, NULL);
if (ret != 0)
{
printf("Create producer thread error\n");
return ret;
}
ret = pthread_create(&thrd_cst_id, NULL, customer, NULL);
if(ret != 0)
{
printf("Create customer thread error\n");
return ret;
}
pthread_join(thrd_prd_id, NULL);
pthread_join(thrd_cst_id, NULL);
close(fd);
unlink(MYFIFO);
return 0;
}
switch(lock.l_type)
{
case F_RDLCK:
{
printf("Read lock set by %d\n", getpid());
}
break;
case F_WRLCK:
{
printf("Write lock set by %d\n", getpid());
}
break;
case F_UNLCK:
{
printf("Release lock by %d\n", getpid());
return 1;
}
break;
default:
break;
}/* end of switch */
return 0;
}
2、测试用主程序
/* write_lock.c */
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include "lock_set.c"
int main(void)
{
int fd;
/* 首先打开文件 */
fd = open("hello", O_RDWR | O_CREAT, 0644);
if(fd < 0)
{
printf("Open file error\n");
exit(1);
}
/* 给文件上写入锁 */
lock_set(fd, F_WRLCK);
getchar();
/* 给文件解锁 */
lock_set(fd, F_UNLCK);
getchar();
close(fd);
return 0;
}
展开阅读全文