【操作系统实践】南航操作系统实践实验报告

操作系统实践实验报告

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 = &params[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);
  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUAA_Peter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值