操作系统实践实验报告
文章目录
job6/sh3.c
(1)题目要求
实现shell程序,要求支持基本命令、重定向命令、管道命令、后台命令
- 使用结构体 tree 描述命令
- 参考代码下载
enum {
TREE_BACK,
TREE_PIPE,
TREE_REDIRICT,
TREE_BASIC,
TREE_TOKEN,
};
#define MAX_CHILD 10
typedef struct {
int type;
char *token;
int child_count;
tree_t *child_vector[MAX_CHILD];
} tree_t;
"echo abc"
+ TREE_BASIC
+ child_vector
+ TREE_TOKEN
+ token = "echo"
+ TREE_TOKEN
+ token = "abc"
"echo abc >log"
+ TREE_REDIRICT
+ TREE_BASIC
+ child_vector
+ TREE_TOKEN
+ token = "echo"
+ TREE_TOKEN
+ token = "abc"
+ TREE_TOKEN
+ token = "<"
+ TREE_TOKEN
+ token = "log"
- 从命令行中读取一行命令,输出该命令的结构
echo abc | wc -l
pipe
basic
echo
abc
basic
wc
-l
cat main.c | grep int | wc -l
pipe
pipe
basic
cat
main.c
basic
grep
int
basic
wc
-l
echo abc | wc -l >log
pipe
basic
echo
abc
redirect
basic
wc
-l
>
log
redirect
pipe
basic
echo
abc
basic
wc
-l
>
log
gcc big.c &
back
basic
gcc
big.c
echo abc | wc -l &
back
pipe
basic
echo
abc
basic
wc
-l
cat <input >output
redirect
redirect
basic
cat
<
input
>
output
(2)解决思路
实验根据老师给的思路进行,首先进行词法分析,分割单词,再进行语法分析,构造语法树结构,再对语法树进行递归执行。
具体的关键函数的解释请见关键代码部分大概的实验过程如下:
实现后台命令执行函数
void tree_execute_async(tree_t *this)//后台命令执行函数
{
tree_t *body = tree_get_child(this, 0);
tree_execute(body);
}
后台执行命令只有child_count
只有两个。cmd &
因为最后一个字符一定是&
,所以直接执行cmd
就好了。
实现基本命令执行函数
对于基本命令,只需要根据其argv[]的参数,调用execvp()函数执行即可。该函数是作为一个基本函数,会被后面的几个执行函数多次调用。
void tree_execute_basic(tree_t *this)//执行普通指令
{
int argc = 0;
char *argv[MAX_ARGC];
int i;
tree_t *child;
vector_each(&this->child_vector, i, child)
argv[argv++] = child->token;
argv[argc] = NULL;
execvp(argv[0], argv);//根据argv[]参数,调用execvp()执行
perror("exec");
exit(EXIT_FAILURE);
}
实现重定向命令执行函数
重定向命令主要有两种情况,一种为"<“作为输入,一种为”>"作为输出,分别建立管道进行连接,再调用tree_execute()函数进行调用
void tree_execute_redirect(tree_t *this)//重定向命令执行函数 cmd >output <input
{
tree_t *body = tree_get_child(this, 0);
tree_t *operator = tree_get_child(this, 1);
tree_t *file = tree_get_child(this, 2);
char *path;
int fd;
int redirect_fd;
//重定向命令主要有两种情况,一种为"<"作为输入,一种为">"作为输出,分别建立管道进行连接
path = file->token;
if (token_is(operator, "<")) {
fd = open(path, O_RDONLY);
assert(fd >= 0);
dup2(fd, 0);
close(fd);
}
if (token_is(operator, ">")) {
fd = creat(path, 0666);
assert(fd >= 0);
dup2(fd, 1);
close(fd);
}
tree_execute(body);
}
实现管道命令执行函数
对于管道命令,需要新开一个进程,重定向管道后,主进程执行右半部分的内容,辅助进程执行前半部分的内容
void tree_execute_pipe(tree_t *this)//实现管道命令执行 cmdA | cmdB
{
int fd[2];
pid_t pid;
tree_t *left = tree_get_child(this, 0);
tree_t *right = tree_get_child(this, 1);
pipe(fd);
pid = fork();
//对于管道命令,需要新开一个进程,重定向管道后,主进程执行右半部分的内容,辅助进程执行前半部分的内容
if (pid == 0) {
close(1);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
tree_execute(left);
exit(EXIT_FAILURE);
}
close(0);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
tree_execute(right);
}
实现自定义命令
sh3中有三个自定义的命令
- exit
- pwd
- cd
按照情况使用if语句按照sh1.c中的函数,分别实现它们即可。
int tree_execute_builtin(tree_t *this)
{
if (this->type != TREE_BASIC)
return 0;
int argc = this->child_vector.count;
tree_t *child0 = tree_get_child(this, 0);
char *arg0 = child0->token;
//sh3中有三个自定义的命令,按照情况使用if语句按照sh1.c中的函数,分别实现它们即可
if (strcmp(arg0, "exit") == 0) {
exit(EXIT_FAILURE);
return 1;
}
if (strcmp(arg0, "pwd") == 0) {
char buf[128];
getcwd(buf, sizeof(buf));
puts(buf);
return 1;
}
if (strcmp(arg0, "cd") == 0) {
if (argc == 1)
return 1;
tree_t *child1 = tree_get_child(this, 1);
char *arg1 = child1->token;
int error = chdir(arg1);
if (error < 0)
perror("cd");
return 1;
}
return 0;
}
(3)关键代码
prase.h中的tree的数据结构
enum {
TREE_ASYNC, // cmd &
TREE_PIPE, // cmdA | cmdB
TREE_REDIRICT, // cmd >output <input
TREE_BASIC, // cmd arg1 arg2
TREE_TOKEN, // leaf
};
typedef struct {
int type;//树的类型
char *token;//命令以词分割的一段字符串
vector_t child_vector; //当前树下子树的数组
} tree_t;
- 其中type是记录这棵树是什么类型的命令,type所有可能通过枚举类型列出,把命令分割成词的token类型。
- 后台命令
- 管道命令
- 基本命令
- 重定向命令
- token字段表示命令以词分割的一段字符串。
- child_vector字段表示当前树下子树的数组。
main.c
- 在main函数中,先判断是否含有
-v
参数,如果有这个参数就会打印语法分析树,然后不断循环跳转到read_and_execute函数以行读取指令。 - 在read_and_execute中以行读取指令,并且跳转到execute_line函数执行指令。
- 在execute_line函数中,先进行词法分析,建立语法分析树,跳转到tree_execute_wrapper函数执行指令。tree_execute_wrapper函数在exec.c中。
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "lex.h"
#include "parse.h"
#include "exec.h"
int verbose = 0;
void execute_line(char *line)//执行函数
{
tree_t *tree;
lex_init(line);//进行词法分析
tree = parse_tree();//建立语法分析树
if (verbose)
tree_dump(tree, 0);//输出打印语法分析树
if (tree != NULL)
tree_execute_wrapper(tree), tree_dump(tree, 0);//执行语法树
lex_destroy();
}
void read_line(char *line, int size)//读取函数
{
int count;
count = read(0, line, size);
if (count == 0)
exit(EXIT_SUCCESS);
assert(count > 0);
if ((count > 0) && (line[count - 1] == '\n'))
line[count - 1] = 0;
else
line[count] = 0;
}
void read_and_execute()
{
char line[128];
write(1, "# ", 2);
read_line(line, sizeof(line));//以行读取指令
execute_line(line);//按行执行
}
void test()
{
execute_line("cat /etc/passwd | sort | grep root >log");
}
int main(int argc, char *argv[])
{
if (argc == 2 && strcmp(argv[1], "-v") == 0)//判断是否有-v参数,有参数,就打印语法分析树,不断循环跳转到read_and_execute函数以行读取指令。
verbose = 1;//判断是否打印语法分析树
while (1)
read_and_execute();//以行读取指令,并且跳转到execute_line函数中执行命令
return 0;
}
exec.c
- 程序运行到tree_execute_wrapper函数,先创建一个子线程,然后跳转到tree_execute函数执行命令。
- 在tree_execute函数中,通过type判断属于什么命令类型。根据命令类型去到相应的执行函数里面,一共有四个具体的执行函数,分别是:tree_execute_async,tree_execute_pipe,tree_execute_basic,tree_execute_redirect。
- 其中tree_execute_async函数,直接执行命令。
- tree_execute_pipe函数,用进程创建子进程,用重定向命令把标准输入输出重定向到管道,分别执行管道左边和右边的命令。
- tree_execute_basic函数,获取命令所有的参数组成参数数组,用execvp函数执行参数数组。
- tree_execute_redirect函数,先判断是重定向输入还是重定向输出,把标准输入输出重定向到文件,最后执行命令。
#include "root.h"
#include "lex.h"
#include "parse.h"
#include "exec.h"
void tree_execute_redirect(tree_t *this)//执行重定向指令
{
tree_t *body = tree_get_child(this, 0);
tree_t *operator = tree_get_child(this, 1);
tree_t *file = tree_get_child(this, 2);
char *path;
int fd;
//重定向命令主要有两种情况,一种为"<"作为输入,一种为">"作为输出,分别建立管道进行连接
path = file->token;
if (token_is(operator, "<")) {
fd = open(path, O_RDONLY);
assert(fd >= 0);
dup2(fd, 0);
close(fd);
}
if (token_is(operator, ">")) {
fd = creat(path, 0666);
assert(fd >= 0);
dup2(fd, 1);
close(fd);
}
tree_execute(body);
}
#define MAX_ARGC 16
void tree_execute_basic(tree_t *this)//执行普通指令
{
int argc = 0;
char *argv[MAX_ARGC];
int i;
tree_t *child;
vector_each(&this->child_vector, i, child)
argv[argc++] = child->token;
argv[argc] = NULL;
execvp(argv[0], argv);//根据argv[]参数,调用execvp()执行
perror("exec");
exit(EXIT_FAILURE);
}
void tree_execute_pipe(tree_t *this)//实现管道命令执行 cmdA | cmdB
{
int fd[2];
pid_t pid;
tree_t *left = tree_get_child(this, 0);
tree_t *right = tree_get_child(this, 1);
//对于管道命令,需要新开一个进程,重定向管道后,主进程执行右半部分的内容,辅助进程执行前半部分的内容
pipe(fd);
pid = fork();
if (pid == 0) {
close(1);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
tree_execute(left);
exit(EXIT_FAILURE);
}
close(0);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
tree_execute(right);
}
int tree_execute_builtin(tree_t *this)//实现自定义命令
{
if (this->type != TREE_BASIC)
return 0;
int argc = this->child_vector.count;
tree_t *child0 = tree_get_child(this, 0);
char *arg0 = child0->token;
//sh3中有三个自定义的命令,按照情况使用if语句按照sh1.c中的函数,分别实现它们即可
if (strcmp(arg0, "exit") == 0) {
exit(EXIT_FAILURE);
return 1;
}
if (strcmp(arg0, "pwd") == 0) {
char buf[128];
getcwd(buf, sizeof(buf));
puts(buf);
return 1;
}
if (strcmp(arg0, "cd") == 0) {
if (argc == 1)
return 1;
tree_t *child1 = tree_get_child(this, 1);
char *arg1 = child1->token;
int error = chdir(arg1);
if (error < 0)
perror("cd");
return 1;
}
return 0;
}
void tree_execute_async(tree_t *this)//后台命令执行函数
{
tree_t *body = tree_get_child(this, 0);
tree_execute(body);
}
//在子进程中执行
void tree_execute(tree_t *this)
{
switch (this->type) {
case TREE_ASYNC:
tree_execute_async(this);
break;
case TREE_PIPE:
tree_execute_pipe(this);
break;
case TREE_REDIRICT:
tree_execute_redirect(this);
break;
case TREE_BASIC:
tree_execute_basic(this);
break;
}
}
// 在主进程中执行
void tree_execute_wrapper(tree_t *this)
{
if (tree_execute_builtin(this))
return;
int status;
pid_t pid = fork();
if (pid == 0) {
tree_execute(this);
exit(EXIT_FAILURE);
}
// cc a-large-file.c &
if (this->type != TREE_ASYNC)
wait(&status);
}
(4)实验结果
实现了shell程序,支持基本命令、重定向命令、管道命令、后台命令
# echo abc | wc -l
1
pipe
basic
echo
abc
basic
wc
-l
# cat main.c | grep int | wc -l
4
pipe
basic
cat
main.c
pipe
basic
grep
int
basic
wc
-l
# echo abc | wc -l >log
pipe
basic
echo
abc
redirect
basic
wc
-l
>
log
# echo abc | wc -l &
async
pipe
basic
echo
abc
basic
wc
-l
# 1
job7/pi2.c
(1)题目要求
莱布尼兹级数公式: 1 - 1/3 + 1/5 - 1/7 + 1/9 - … = PI/4
主线程创建N个辅助线程
每个辅助线程计算一部分任务,并将结果返回
主线程等待N个辅助线程运行结束,将所有辅助线程的结果累加
本题要求 1: 使用线程参数,消除程序中的代码重复
本题要求 2:不能使用全局变量存储线程返回值
(2)解决思路
- 定义线程参数结构体,消除代码重复,简化程序逻辑
- 分为主线程和辅助线程,共N个辅助线程
- 主线程中利用
for
循环创建N个线程,并等待N个线程的结果返回后,读取线程的返回值,将返回值进行合并计算除最后的结果 - 辅助线程中计算划分的每一段的结果,利用线程参数将计算结果返回给主线程
- 主线程中利用
(3)关键代码
- 线程参数结构体
struct param
{
int start;//开始数字,如对于1/3,这个数字就是1,即2i+1=3
int end;//结束数字
};
struct result
{
double sum;//辅助线程内计算结果sum,返回到主线程
};
- 线程启动函数
void compute(void*arg)
void *compute(void *arg)
{
struct param *param;
struct result *result;//保存线程的计算结果
double sum = 0;
param = (struct param *)arg;//将传递进来的参数强制转换为进程参数结构体形式
for(int i=param->start;i<param->end;i++)//具体计算级数的逻辑:分奇偶计算即可
{
double num = i;
if(i%2==1)
{
if((i+1)%4==0)
{
sum-=1.0/num;
}
else
{
sum+=1.0/num;
}
}
}
result = malloc(sizeof(struct result));//申请一个空间存储线程计算结果
result->sum = sum;
return result;//将线程计算结果返回给主进程
}
- 主线程创建线程并等待线程
int main()
{
pthread_t worker_tids[CORE_N];//CORE_N为并行计算的线程数
struct param params[CORE_N];//每一个线程的线程参数
double total=0;//最后计算的结果
for(int i=0;i<CORE_N;i++)//创建CORE_N个线程
{
struct param *param;
param = ¶ms[i];
param -> start = i*END/CORE_N;
param -> end = (i+1)*END/CORE_N;
pthread_create(&worker_tids[i],NULL,compute,param);
}
for(int i=0;i<CORE_N;i++)//等待CORE_N个计算结果并释放内存,合并得到最终结果
{
struct result *result;
pthread_join(worker_tids[i],(void**)&result);
printf("CPU%d sum = %f\n",i,result->sum);
total += result->sum;
free(result);
}
total*=4;
printf("pi = %f\n",total);//输出最终计算结果
return 0;
}
(4)运行结果
-
END取10000
-
CORE_N 取10
-
每一个线程输出自己的计算结果,最后综合后,得到 π \pi π的近似值。如下所示
CPU0 sum = 0.785899
CPU1 sum = -0.000251
CPU2 sum = -0.000083
CPU3 sum = -0.000042
CPU4 sum = -0.000025
CPU5 sum = -0.000017
CPU6 sum = -0.000012
CPU7 sum = -0.000009
CPU8 sum = -0.000007
CPU9 sum = -0.000006
pi = 3.141793
job8/pc.c
(1)题目要求
+ 系统中有3个线程:生产者、计算者、消费者
+ 系统中有2个容量为4的缓冲区:buffer1、buffer2
+ 生产者
- 生产'a'、'b'、'c'、‘d'、'e'、'f'、'g'、'h'八个字符
- 放入到buffer1
- 打印生产的字符
+ 计算者
- 从buffer1取出字符
- 将小写字符转换为大写字符,按照 input:OUTPUT 的格式打印
- 放入到buffer2
+ 消费者
- 从buffer2取出字符
- 打印取出的字符
+ 程序输出结果(实际输出结果是交织的)
a
b
c
...
a:A
b:B
c:C
...
A
B
C
...
(2)解决思路
-
在原有的生产者消费者的代码上进行改进->生产者、计算者、消费者模型
-
生产者写入数据到buffer1[CAPACITY],计算者取buffer1[CAPCITY]中的数据,转为大写后写入buffer2[CAPACITY],消费者取buffer2[CAPACITY]中的数据输出
-
修改函数
int buffer1_is_empty()//buffer1是否为空 int buffer1_is_full()//buffer1是否为满 int get_item1()//取buffer1中的数据 void put_item1(int item)//将item放入buffer1中 int buffer2_is_empty()//buffer2是否为空 int buffer2_is_full()//buffer2是否为满 int get_item2()//取buffer2中的数据 void put_item2(int item)//将item放入buffer2中
-
重新定义条件变量
- wait_empty_buffer1
- wait_empty_buffer2
- wait_full_buffer1
- wait_full_buffer2
-
(3)关键代码
- 生产者函数
void *produce(void *arg)//生产者函数
{
int i;
int item;
for(i=0;i < ITEM_COUNT;i++)
{
pthread_mutex_lock(&mutex);//使用互斥量进程保护
while(buffer1_is_full())//当buffer1是满时,等待一个空位
{
pthread_cond_wait(&wait_empty_buffer1,&mutex);
}
item = 'a' + i;
put_item1(item);
printf("%c\n",item);
pthread_cond_signal(&wait_full_buffer1);//buffer1内不为空的数据位+1
pthread_mutex_unlock(&mutex);
}
return NULL;
}
- 计算者函数
void *commpute(void *arg)//计算者函数
{
int i;
int item,ITEM;
for(i=0;i<ITEM_COUNT;i++)
{
pthread_mutex_lock(&mutex);//使用互斥量进行保护
while(buffer1_is_empty())//当buffer1为空时,等待buffer1内有数据再执行
{
pthread_cond_wait(&wait_full_buffer1,&mutex);
}
item = get_item1();
pthread_cond_signal(&wait_empty_buffer1);//取走数据后,buffer1内为空的数据位+1
pthread_mutex_unlock(&mutex_pc);//解锁
pthread_mutex_lock(&mutex);//使用互斥量进行保护
while(buffer2_is_full())//当buffer2为满时,等待buffer2内有空位再执行
{
pthread_cond_wait(&wait_empty_buffer2,&mutex);
}
ITEM = item+'A'-'a';//小写转大写
put_item2(ITEM);
printf("\t%c:%c\n",item,ITEM);
pthread_cond_signal(&wait_full_buffer2);//写入数据后,buffer2内不为空的数据位+1
pthread_mutex_unlock(&mutex);//解锁
}
return NULL;
}
- 消费者函数
void *consume(void *arg)//消费者函数
{
int i;
int item;
for(i = 0;i<ITEM_COUNT;i++)
{
pthread_mutex_lock(&mutex);//使用互斥量进行保护
while(buffer2_is_empty())//当buffer2是空时,等待buffer2内有数据再执行
{
pthread_cond_wait(&wait_full_buffer2,&mutex);
}
item = get_item2();//取数据输出
printf("\t\t%c\n",item);
pthread_cond_signal(&wait_empty_buffer2);//取走数据后,buffer2内为空的数据位+1
pthread_mutex_unlock(&mutex);//解锁
}
return NULL;
}
- 主函数
int main()
{
pthread_t computer_tid;//计算者线程
pthread_t consumer_tid;//消费者线程
//互斥量初始化
pthread_mutex_init(&mutex,NULL);
//条件变化初始化
pthread_cond_init(&wait_empty_buffer1,NULL);
pthread_cond_init(&wait_full_buffer1,NULL);
pthread_cond_init(&wait_empty_buffer2,NULL);
pthread_cond_init(&wait_full_buffer2,NULL);
pthread_create(&computer_tid,NULL,commpute,NULL);//创建计算者线程
pthread_create(&consumer_tid,NULL,consume,NULL);//创建消费者线程
produce(NULL);
pthread_join(computer_tid,NULL);//等待计算者线程
pthread_join(consumer_tid,NULL);//等待消费者进程
return 0;
}
(4)运行结果
- 生产者、计算者、消费者的运行结果如下所示
a
b
c
d
a:A
A
e
b:B
f
c:C
C
g
d:D
D
h
e:E
f:F
g:G
E
F
G
h:H
H
job9/pc.c
(1)题目要求
使用信号量解决生产者、计算者、消费者问题
功能与 job8/pc.c
相同
(2)解决思路
- 在job8/pc.c 的基础上进行修改
- 引入信号量结构体
- 定义多个信号量
- sema_t mutex1_sema;
- sema_t mutex2_sema;
- sema_t empty_buffer1_sema;
- sema_t empty_buffer2_sema;
- sema_t full_buffer1_sema;
- sema_t full_buffer2_sema;
- 通过sema_init()对信号量进行初始化
- 通过sema_wait()进行等待,相当于操作系统中的P操作
- 通过sema_signal()进行释放,相当于操作系统中的V操作
- 定义多个信号量
- 删除job8/pc.c 中的判断数组是否为空为满的结构体
- 对get_item()以及put_item()函数进行基础,减少重复的部分
- 引入信号量结构体
(3)关键代码
- 信号量结构体 sema_t
typedef struct//信号量结构体
{
int value;
pthread_mutex_t mutex;
pthread_cond_t cond;
}sema_t;
- 信号量机制处理相关函数
void sema_init(sema_t *sema,int value)//对信号量进行初始化
{
sema->value = value;
pthread_mutex_init(&sema->mutex,NULL);
pthread_cond_init(&sema->cond,NULL);
}
void sema_wait(sema_t *sema)//相当于操作系统中的P操作
{
pthread_mutex_lock(&sema->mutex);//使用互斥量进行保护
while(sema->value<=0)//当value值<0时等待
pthread_cond_wait(&sema->cond,&sema->mutex);
sema->value--;//相当于占用一个
pthread_mutex_unlock(&sema->mutex);//解锁
}
void sema_signal(sema_t *sema)//相当于操作系统中的V操作
{
pthread_mutex_lock(&sema->mutex);//使用互斥量进行保护
++sema->value;//相当于释放一个
pthread_cond_signal(&sema->cond);//唤醒
pthread_mutex_unlock(&sema->mutex);//解锁
}
- 相关定义变量
sema_t mutex1_sema;
sema_t mutex2_sema;
sema_t empty_buffer1_sema;
sema_t empty_buffer2_sema;
sema_t full_buffer1_sema;
sema_t full_buffer2_sema;
- 生产者函数
void *produce(void *arg)//生产者函数
{
int i;
int item;
for(i=0;i<ITEM_COUNT;i++)
{
sema_wait(&empty_buffer1_sema);//等待一个可以写入的空位
sema_wait(&mutex1_sema);//保护
item = i+'a';
put_item1(item);
printf("%c\n",item);
sema_signal(&mutex1_sema);//解锁
sema_signal(&full_buffer1_sema);//相当于已经写入的位数+1
}
return NULL;
}
- 计算者函数
void *commpute(void *arg)
{
int i;
int item;
int ITEM;
for(i=0;i<ITEM_COUNT;i++)
{
sema_wait(&full_buffer1_sema);//等待buffer1有已经写入的数据
sema_wait(&mutex1_sema);//保护
item = get_item1();
ITEM = item+'A'-'a';
printf("\t %c:%c\n",item,ITEM);//输出
sema_signal(&mutex1_sema);//解锁
sema_signal(&empty_buffer1_sema);//相当于buffer1可写入的空位+1
sema_wait(&empty_buffer2_sema);//等待buffer2中有可写入的空位
sema_wait(&mutex2_sema);//保护
put_item2(ITEM);
sema_signal(&mutex2_sema);//解锁
sema_signal(&full_buffer2_sema);//相当于buffer1已写入位+1
}
return NULL;
}
- 消费者函数
void *consume(void *arg)
{
int i;
int item;
for(i=0;i<ITEM_COUNT;i++)
{
sema_wait(&full_buffer2_sema);//等待buffer2有已经写入的数据
sema_wait(&mutex2_sema);//保护
item = get_item2();
printf("\t\t%c\n",item);//输出
sema_signal(&mutex2_sema);//解锁
sema_signal(&empty_buffer2_sema);//相当于释放buffer2一个可写入的空位
}
return NULL;
}
(4)运行结果
- 于job8/pc.c一致
a
b
c
d
a:A
A
e
b:B
f
c:C
C
g
d:D
D
h
e:E
f:F
g:G
E
F
G
h:H
H
job10/pfind.c
(1)题目要求
程序 sfind 在文件或者目录中查找指定的字符串,并打印包含该字符串的行,示例如下:
-在文件 file.c 中查找字符串 main
+找到包含字符串 main 的行
+打印文件名和该行
-在目录 test 中查找字符串 main
-假设目录 test 下存在文件
+test/hello/hello.c
+test/world/world.c
对目录 test下的所有文件进行查找
找到包含字符串 main 的行 打印文件名和该行
pfind功能与 sfind 相同
要求使用多线程完成
主线程创建若干个子线程
主线程负责遍历目录中的文件
遍历到目录中的叶子节点时
将叶子节点发送给子线程进行处理
两者之间使用生产者消费者模型通信
主线程生成数据
子线程读取数据
(2)解决思路
- 根据sfind.c的思路进行改进
- 主线程负责递归遍历目录找到叶子结点,并将路径加入到任务队列中(任务队列类似于生产者消费者问题,主线程类似于生产者)
- 子线程负责从任务队列取出任务,并处理子串问题,类似于消费者
- 特殊任务:is_end属性,如果子任务结束,就要产生特殊任务,告诉进程结束任务。
- 对于生产者消费者问题,可以job8与job9中的条件变量或者信号量机制来实现,这里选择使用信号量机制进行实现
- 这里根据老师所给的代码思路进行编写
#define WORKER_NUMBER 4
struct task {
int is_end;
char path[128];
char string[128];
};
+ 子线程的入口
- 在一个循环中
- 从任务队列中,获取一个任务,去执行
- 当读取到一个特殊的任务(is_end 为真),循环结束
void *worker_entry(void *arg)
{
while (1)
{
struct task task;
从任务队列中获取一个任务 task;
if (task->is_end)
break;
执行该任务;
find_file(task->path, task->string);
}
}
int main(int argc, char **argv[])
{
char *path = argv[1];
char *string = argv[2];
if (path 是一个普通文件)
{
find_file(path, string);
return;
}
1. 创建一个任务队列;
- 初始化时,任务队列为空
2. 创建 WORKER_NUMBER 个子线程;
3. 对目录 path 进行递归遍历:
- 遇见叶子节点时
- 把叶子节点的路径加入到任务队列中
4. 创建 WORER_NUMBER 个特殊任务
- 特殊任务的 is_end 为真
* 子线程读取到特殊任务时
* 表示主线程已经完成递归遍历,不会再向任务队列中放置任务
* 此时,子线程可以退出
- 把这些特殊任务加入到任务队列中
5. 等待所有的子线程结束;
}
(3)关键代码
- 信号量机制的结构体及wait()signal()init()函数在这里省略,可以参照上一个作业中的关键函数
- sema_t mutex_sema
- sema_t empty_buffer_sema
- sema_t full_buffer_sema
- 任务结构体及任务队列
#define WORKER_NUMBER 4
#define QUEUELENGTH 8
typedef struct//任务结构体
{
int is_end;//is_end表示主线程已经完成递归遍历,不会再向任务队列中放置任务
char path[128];//路径
char string[128];//查询的字符串
}TASK;
TASK task_queue[QUEUELENGTH];//任务队列
int in,out;
void find_file(char *path, char *target)
实现递归查询的关键函数,看对应路径中是否有目标target
出现
void find_file(char *path, char *target)//查询path文件中是否存在目标target,有则输出一行
{
FILE *file = fopen(path, "r");
char line[256];
while (fgets(line, sizeof(line), file))
{
if (strstr(line, target))
printf("%s: %s", path, line);
}
fclose(file);
}
void find_dir(char *path, char *target)
实现递归查询的关键函数,相当于生产者,看对应路径下是否有目标target
出现,如果是文件就将该文件的相关查找及路径进行写入结构体task中,加入任务队列;如果是目录则递归调用find_dir()
函数到下一级目录中进行查找
void find_dir(char *path, char *target)
{
DIR *dir = opendir(path);//打开目录
struct dirent *entry;
while (entry = readdir(dir))
{
if (strcmp(entry->d_name, ".") == 0)
continue;
if (strcmp(entry->d_name, "..") == 0)
continue;
if (entry->d_type == DT_DIR)//是目录则进入下一目录中,递归调用
{
char newdirpath[256];
strcpy(newdirpath,path);
strcat(newdirpath,"/");
strcat(newdirpath,entry->d_name);
find_dir(newdirpath,target);
}
if (entry->d_type == DT_REG)//是文件则直接进行查找,将该文件作为一个任务放入任务队列中
{
sema_wait(&empty_buffer_sema);//等待任务队列中有空位
sema_wait(&mutex_sema);//保护
task_queue[in].is_end = 0;//非特殊任务
strcpy(task_queue[in].path,path);
strcat(task_queue[in].path,"/");
strcat(task_queue[in].path,entry->d_name);
strcpy(task_queue[in].string,target);
in = (in + 1) % QUEUELENGTH;//加入任务队列中
sema_signal(&mutex_sema);//解锁
sema_signal(&full_buffer_sema);//释放,不为空的队列位+1
}
}
closedir(dir);
}
void *worker_entry(void *arg)
相当于消费者函数
void *worker_entry(void *arg)//消费者函数
{
while(1)
{
TASK *task;
sema_wait(&full_buffer_sema);//等待当前任务队列中有数据
sema_wait(&mutex_sema);//保护
task = &task_queue[out];//取任务
out = (out+1)%QUEUELENGTH;
sema_signal(&mutex_sema);//解锁
sema_signal(&empty_buffer_sema);//当前队列为空的数据位+1;
if(task->is_end)
break;
find_file(task->path,task->string);
}
}
- 主函数
按照老师所给的代码思路进行书写
int main(int argc, char *argv[])
{
if (argc != 3) {
puts("Usage: sfind file string");
return 0;
}
char *path = argv[1];
char *string = argv[2];
struct stat info;
stat(path, &info);//路径转换
if (S_ISREG(info.st_mode))//是文件则直接进行查找
{
find_file(path, string);
return 0;
}
//信号量定义
sema_init(&mutex_sema, 1);
sema_init(&empty_buffer_sema, QUEUELENGTH - 1);
sema_init(&full_buffer_sema, 0);
//线程定义
pthread_t worker_tid[WORKER_NUMBER];
for(int i=0;i<WORKER_NUMBER;i++)//创建WORKER_NUMBER个线程,调用消费者函数worker_entry()
pthread_create(&worker_tid[i],NULL,worker_entry,NULL);
find_dir(path,string);//相当于生产者函数,对目录path进行递归遍历,把叶子结点的路径加入到任务队列中
for(int i=0;i<WORKER_NUMBER;i++)//创建WORKER_NUMBER个特殊任务,将特殊任务加入到队列中
{
sema_wait(&empty_buffer_sema);
sema_wait(&mutex_sema);
task_queue[in].is_end = 1;
in = (in + 1) % QUEUELENGTH;
sema_signal(&mutex_sema);
sema_signal(&full_buffer_sema);
}
for(int i=0;i<WORKER_NUMBER;i++)//等待所有子进程结束
pthread_join(worker_tid[i], NULL);
return 0;
}
(4)实验结果
nuaapeter@DESKTOP-F8DDVJ2:~$ gcc pind.c -lpthread
nuaapeter@DESKTOP-F8DDVJ2:~$ ./a.out review int
review/job3/myecho: int main()
review/job3/myecho: int fd;
review/job3/mycp.c: int main()
review/job3/mycp.c: int pid;
review/job3/mycp.c: int fd[2];
review/job3/mycp.c: int count;
review/job3/mycp.c: int fd;
review/job3/mysys: int main(int argc,char *argv[])
review/job3/mysys: int count;
review/job3/mysys: printf("%s\n",str);
review/job3/sh1.c: int main(int argc,char* argv[])
review/job3/sh1.c: printf("ENTER PWD!");
review/job3/sh1.c: int fp;
review/job3/sh1.c: printf("open failed!\n");
review/job3/sh1.c: int flag;
review/job3/sh1.c: printf("%s",buf);