文章目录
job6/sh3.c
题目要求
实现shell程序,要求支持基本命令、重定向命令、管道命令、后台命令。
解决思路
- type类型有TREE_BASICTREE_ASYNC,TREE_PIPE,TREE_REDIRECT,TREE_BASIC,TREE_TOKEN五种,分别是后台类型,管道类型,重定向类型,基本类型,叶子节点。tree_t结构体的type存放类型,token存放叶子节点的单词,child_count孩子数量(比如类型为TREE_PIPE的节点有两个孩子子树),child_vector指向子树;
- 根据节点类型不同,选择不同的子节点存放方式:
- 每个type为TREE_BASIC的子节点中存放单词;
- 每个type为TREE_REDIRECT的第一个子节点中存放“>”或">>“或”<",其他节点中存放basic节点;
- 每个type为TREE_PIPE的两个子节点中存放对应节点(可以是basic\redirect\pipe);
- 如果type为TREE_ASYNC,一定是最后一个节点。
- 从根节点开始,每个节点tree_t执行exec;
- 在exec中通过type进行判断:
- 如果是TREE_PIPE,执行exec_pipe,exec_pipe中将 "|"左边的输出重定向至管道的写端,将“|”右边的输入重定向至管道的读端;
- 如果是TREE_REDIRECT,执行exec_redirect,exec_redirect判断这个节点的第一个子节点,可能为“>”或“>>”或“<”,分别执行写文件重定向至标准输出、追加文件重定向至标准输出、读文件重定向至标准输入。
- 如果是TREE_BASIC,执行exec_basic,除了“cd”、"pwd"操作,其他使用execvp()函数执行操作。
- 在main中,如果某节点类型为TREE_ASYNC,则不等待子进程结束,就可以执行下一个循环,以此实现后台命令。
关键代码
基本操作的执行,如果是cd操作就直接退出,pwd操作使用getcwd获取路径,其他的命令使用execvp装入程序
void exec_basic(tree_t *command_tree)
{
if(strcmp(command_tree->child_vector[0]->token, "cd") == 0)
{
return;
}
else if(strcmp(command_tree->child_vector[0]->token, "pwd") == 0)
{
char *path = getcwd(NULL, 0);
printf("%s\n", path);
free(path);
// exit(0);
}
else
{
int argc = command_tree->child_count;
char *argv[argc+1];
for(int i=0; i<argc; i++){
argv[i] = command_tree->child_vector[i]->token;
}
argv[argc] = NULL;
int error = execvp(argv[0], argv);
if(error < 0)
{
perror(argv[0]);
}
}
}
管道操作,左子树的输出重定向到管道的写端,右子树的输入重定向到管道的读端。
void exec_pipe(tree_t *command_tree)
{
int fd[2];
int pid;
pipe(fd);
pid = fork();
if(pid == 0){
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
exec(command_tree->child_vector[0]);
exit(0);
}
wait(NULL);
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
exec(command_tree->child_vector[1]);
}
重定向操作,第一个孩子的token为“<”,打开文件读;为“>”,打开文件写;为“>>”,打开文件追加。
void exec_redirect(tree_t *command_tree)
{
int fd;
if(strcmp(command_tree->child_vector[1]->token, "<") == 0)
{
fd = open(command_tree->child_vector[2]->token, O_RDONLY, 0666);
dup2(fd, 0);
close(fd);
}
else if(strcmp(command_tree->child_vector[1]->token, ">") == 0)
{
fd = open(command_tree->child_vector[2]->token, O_CREAT|O_RDWR, 0666);
dup2(fd, 1);
close(fd);
}
else
{
fd = open(command_tree->child_vector[2]->token, O_APPEND|O_CREAT|O_RDWR, 0777);
dup2(fd, 1);
close(fd);
}
exec(command_tree->child_vector[0]);
}
执行代码
void exec(tree_t *command_tree)
{
switch(command_tree->type)
{
case TREE_BASIC:
exec_basic(command_tree);
break;
case TREE_PIPE:
exec_pipe(command_tree);
break;
case TREE_REDIRECT:
exec_redirect(command_tree);
break;
default:
break;
}
mian函数,循环接收输出的指令,根据指令构建语法树,exit指令就结束程序,cd指令就调用chdir,其他指令使用子进程调用exec,后台命令就不用等待子进程结束。
int main()
{
char line[SIZE];
int count;
while(1){
write(1, ">", sizeof(">"));
count = read(0, line, sizeof(line)) -1;
line[count] = 0;
char *word_array[MAX_ARGC];
int word_count = split(line, " ", word_array);
tree_t *root = func(word_array, word_count);
// tree_print(root,0);
int async = 0;
if(root->type == TREE_ASYNC){
async = 1;
root = root->child_vector[0];
}
if(root->child_vector[0]->token != NULL && strcmp(root->child_vector[0]->token, "exit") == 0){
exit(0);
}
if(root->child_vector[0]->token != NULL && strcmp(root->child_vector[0]->token, "cd") == 0){
// printf("cd\n");
int error = chdir(root->child_vector[1]->token);
if(error < 0){
perror("cd");
}
}
else{
pid_t pid = fork();
if(pid == 0){
exec(root);
}
}
if(!async)
wait(NULL);
}
return 0;
}
运行结果
实现了shell程序,支持基本命令、重定向命令、管道命令、后台命令。
//基本输入
echo abc ddd
//输出
abc ddd
//重定向输入
echo abc >log
//输出
log文件中内容:abc
//管道输入
cat </etc/passwd | wc -l >log
//输出
log文件中内容:29
//后台输入
cat /etc/passwd >log &
//输出
log文件中内容是/etc/passwd中的内容
job7/pi2.c
题目要求
使用N个线程根据莱布尼兹级数计算PI
- 能适应N个核心
- 主线程创建N个辅助线程
- 每个辅助线程计算一部分任务,并将结果返回
- 主线程等待N个辅助线程运行结束,将所有辅助线程的结果累加
- 本题要求 1: 使用线程参数,消除程序中的代码重复
- 本题要求 2: 不能使用全局变量存储线程返回值
解决思路
- 在main中创建多个子线程,每个子线程执行compute ,子线程参数为param结构体,param中有compute累加需要的start与end参数;
- 在compute函数中,初始化sum=0,i从param->start开始增加,判断i,若i是奇数且i+1是4的倍数,sum减去i的倒数,若i是奇数且i+1不是4的倍数,sum加上i的倒数。循环直到i等于param->end,返回sum;
- main中等待所有子线程结束,获得所有子线程的返回值,将所有返回值累加再乘以4,则得到PI;
- 每个子线程的start与end由核心数CORE_N与END决定。
关键代码
参数结构体,返回值结构体
struct param{
int start;
int end;
};
struct result{
double sum;
};
计算函数
i从param->start开始增加,判断i,若i是奇数且i+1是4的倍数,sum减去i的倒数,若i是奇数且i+1不是4的倍数,sum加上i的倒数。循环直到i等于param->end,返回sum。
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)
{
// printf("-1/%d",i);
sum-=1.0/num;
}
else
{
// printf("+1/%d",i);
sum+=1.0/num;
}
}
}
result = malloc(sizeof(struct result));
result->sum = sum;
return result;
}
主函数循环创建多个子线程,并等待子线程结束获得返回值,计算得PI。
int main()
{
pthread_t worker_tids[CORE_N];
struct param params[CORE_N];
double total=0;
for(int i=0;i<CORE_N;i++)
{
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++)
{
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;
}
运行结果
当END取9999,CORE_N 取 10时,
结果为:PI=3.141793
job8/pc.c
题目要求
使用条件变量解决生产者、计算者、消费者问题
- 系统中有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 ...
解决思路
- 创建两个缓冲区,buffer1中存放小写字母,buffer2中存放大写字母;
- get_item1()与put_item1()对buffer1进行操作,get_item2()与put_item2()对buffer2进行操作,
buffer1_is_empty()与buffer1_is_full()对buffer1进行判断,buffer2_is_empty()与buffer2_is_full()对buffer2进行判断; - main函数中创建子线程compute_tid与consumer_tid,入口函数分别是compute()与consume();
- 主线程调用produce(),如果buffer1是满的,则等待条件变量wait_empty_buffer1,向buffer1中放入产生的字母,并将发出buffer1满的信号;
- 子线程computer_tid调用compute(),buffer1是空的,则等待条件变量wait_full_buffer1,从buffer1中取出字母,并将发出buffer1空的信号。buffer2是满的则,等待条件变量wait_empty_buffer2,将小写字母转换为大写字母,向buffer2中放入产生的大写字母,并将发出buffer2满的信号;
- 子线程consumer_tid调用consume(),buffer2是空的,则等待条件变量wait_full_buffer2,从buffer2中取出字母,并将发出buffer2空的信号;
- main函数等待所有子线程结束再结束。
关键代码
生产者
互斥访问buffer1,等待条件变量wait_empty_buffer1,产生小写字母。
void *produce(void *arg)
{
int i;
int item;
for(i=0;i < ITEM_COUNT;i++)
{
pthread_mutex_lock(&mutex_pc);
while(buffer1_is_full())
{
pthread_cond_wait(&wait_empty_buffer1,&mutex_pc);
}
item = 'a' + i;
put_item1(item);
printf("%c\n",item);
pthread_cond_signal(&wait_full_buffer1);
pthread_mutex_unlock(&mutex_pc);
}
return NULL;
}
计算者
互斥访问buffer1,等待条件变量wait_full_buffer1,获取小写字母。
互斥访问buffer2,等待条件变量wait_empty_buffer2,产生大写字母。
//a:A
void *commpute(void *arg)
{
int i;
int item,ITEM;
for(i=0;i<ITEM_COUNT;i++)
{
pthread_mutex_lock(&mutex_pc);
while(buffer1_is_empty())
{
pthread_cond_wait(&wait_full_buffer1,&mutex_pc);
}
item = get_item1();
pthread_cond_signal(&wait_empty_buffer1);
pthread_mutex_unlock(&mutex_pc);
pthread_mutex_lock(&mutex_cc);
while(buffer2_is_full())
{
pthread_cond_wait(&wait_empty_buffer2,&mutex_cc);
}
ITEM = item+'A'-'a';
put_item2(ITEM);
printf("\t%c:%c\n",item,ITEM);
pthread_cond_signal(&wait_full_buffer2);
pthread_mutex_unlock(&mutex_cc);
}
return NULL;
}
消费者
互斥访问buffer2,等待条件变量wait_full_buffer2,获取大写字母。
void *consume(void *arg)
{
int i;
int item;
for(i = 0;i<ITEM_COUNT;i++)
{
pthread_mutex_lock(&mutex_cc);
while(buffer2_is_empty())
{
pthread_cond_wait(&wait_full_buffer2,&mutex_cc);
}
item = get_item2();
printf("\t\t%c\n",item);
pthread_cond_signal(&wait_empty_buffer2);
pthread_mutex_unlock(&mutex_cc);
}
return NULL;
}
主函数
初始化互斥量、条件变量,创建子线程执行计算与消费,调用生产者,等待所有线程结束。
int main()
{
pthread_t computer_tid;
pthread_t consumer_tid;
pthread_mutex_init(&mutex_pc,NULL);
pthread_mutex_init(&mutex_cc,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;
}
运行结果
a
b
c
d
a:A
A
e
b: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
题目要求
使用信号量解决生产者、计算者、消费者问题
- 系统中有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 ...
解决思路
- 创建两个buffer,buffer1中存放小写字母,buffer2中存放大写字母;
- get_item1()与put_item1()对buffer1进行操作,get_item2()与put_item2()对buffer2进行操作,
buffer1_is_empty()与buffer1_is_full()对buffer1进行判断,buffer2_is_empty()与buffer2_is_full()对buffer2进行判断; - 建立信号量sema_t结构体,包含信号量的值、互斥量、条件变量。
- sema_init():信号量初始化,
- sema_wait():如果信号量的值小于等于0,则等待条件变量,将信号量的值减一
- sema_signal():将信号量的值加一,唤醒等待条件变量的线程
- mutex1_sema 、 mutex2_sema用于互斥访问共享缓冲区变量in1、out1、in2、out2
- empty_buffer1_sema、full_buffer1_sema、empty_buffer2_sema、full_buffer3_sema用于线程同步;
- 主线程调用produce(),生产者需要一个空的buffer1,申请信号量empty_buffer1_sema,mutex1_sema用于对共享变量进行互斥访问,生产者线程生产一个数据后产生一个新的满buffer1,释放信号量full_buffer1_sema。
- 子线程computer_tid调用compute():
- full_buffer1_sema的数值表示空buffer的数量,消费者需要一个满的buffer1,申请请信号量full_buffer1_sema,mutex1_sema用于对共享变量进行互斥访问,消费者线程取走一个数据后,产生一个新的空buffer1,释放信号量empty_buffer1_sema。
- 将小写字母转换为大写字母。
- 还需要一个空的buffer2,申请信号量empty_buffer2_sema,mutex2_sema用于对共享变量进行互斥访问,生产者线程生产一个数据后产生一个新的满buffer2,释放信号量full_buffer2_sema。
- 子线程consumer_tid调用consume(),full_buffer2_sema的数值表示空buffer的数量,消费者需要一个满的buffer2,申请请信号量full_buffer2_sema,mutex2_sema用于对共享变量进行互斥访问,消费者线程取走一个数据后,产生一个新的空buffer2,释放信号量empty_buffer2_sema。
- 初始化信号量,创建消费者线程,主线程作为生产者,执行produce函数,子线程compute_tid与consumer_tid,入口函数分别是compute()与consume()。main函数等待所有子线程结束再结束。
关键代码
生产者
等待信号量empty_buffer1_sema,等待信号量mutex1_sem,产生小写字母。
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);
}
return NULL;
}
计算者
等待信号量full_buffer1_sema,等待信号量mutex1_sem,获取小写字母。
等待信号量empty_buffer2_sema,等待信号量mutex2_sem,产生大写字母。
void *commpute(void *arg)
{
int i;
int item;
int ITEM;
for(i=0;i<ITEM_COUNT;i++)
{
sema_wait(&full_buffer1_sema);
sema_wait(&mutex1_sema);
item = get_item1();
ITEM = item+'A'-'a';
// printf("commpute:%c\n",item);
printf("\t %c:%c\n",item,ITEM);
sema_signal(&mutex1_sema);
sema_signal(&empty_buffer1_sema);
sema_wait(&empty_buffer2_sema);
sema_wait(&mutex2_sema);
put_item2(ITEM);
sema_signal(&mutex2_sema);
sema_signal(&full_buffer2_sema);
}
return NULL;
}
消费者
等待信号量full_buffer2_sema,等待信号量mutex2_sem,获取大写字母。
void *consume(void *arg)
{
int i;
int item;
for(i=0;i<ITEM_COUNT;i++)
{
sema_wait(&full_buffer2_sema);
sema_wait(&mutex2_sema);
item = get_item2();
printf("\t\t%c\n",item);
sema_signal(&mutex2_sema);
sema_signal(&empty_buffer2_sema);
}
return NULL;
}
主函数
初始化信号量,创建子线程执行计算与消费,调用生产者,等待所有线程结束。
int main()
{
pthread_t commpute_tid;
pthread_t consume_tid;
sema_init(&mutex1_sema,1);
sema_init(&mutex2_sema,1);
sema_init(&empty_buffer1_sema,CAPACITY-1);
sema_init(&full_buffer1_sema,0);
sema_init(&empty_buffer2_sema,CAPACITY-1);
sema_init(&full_buffer2_sema,0);
pthread_create(&commpute_tid,NULL,commpute,NULL);
pthread_create(&consume_tid,NULL,consume,NULL);
produce(NULL);
pthread_join(commpute_tid,NULL);
pthread_join(consume_tid,NULL);
return 0;
}
运行结果
a
b
c
d
a:A
A
e
b: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
题目要求
- 功能
- 功能与 sfind 相同
- 要求使用多线程完成
- 主线程创建若干个子线程
- 主线程负责遍历目录中的文件
- 遍历到目录中的叶子节点时
- 将叶子节点发送给子线程进行处理
- 两者之间使用生产者消费者模型通信
- 主线程生成数据
- 子线程读取数据
- 主线程创建若干个子线程
- 图示
- 主线程创建 2 个子线程
- 主线程遍历目录 test 下的所有文件
- 把遍历的叶子节点 path 和目标字符串 string,作为任务,发送到任务队列
- 子线程
- 不断的从任务队列中读取任务 path 和 string
- 在 path 中查找字符串 string
解决思路
- buffer格式为task结构体,task存储is_end判断是否是最后一个文件,path找到的文件的路径,string含有要查找单词的行;
- buffer_is_empty()与buffer_is_full()对buffer进行判断,mutex对buffer进行互斥访问, wait_empty_buffer、wait_full_buffer两个条件变量用于存buffer、取buffer。
- 在get_item与put_item中互斥访问buffer:
- get_item中,如果buffer_is_empty,要等条件变量wait_full_buffer再取文件,并设置wait_empty_buffer
- put_item中,如果buffer_is_full,要等条件变量wait_empty_buffer再存文件,并设置wait_full__buffer。
- 在文件中查找含有单词的行,逐行读取文件,再使用strstr()即可;
- 子线程入口函数worker_entry中有get_item,获取要处理的文件,如果是最后一个文件,在结束子进程之前,再向buffer中放入一个is_end=1的task,让其他的子进程也可以结束。
- 主线程要找出某目录下所有的文件,使用find_dir函数,opendir()进入目录,判断每个元素,是文件就执行put_item放入buffer,是目录就加入目录名字递归查找。
- 主函数 先判断目标路径是不是文件,是文件就直接find_file,不是就find_dir。添加一个is_end=1的task作为结束的元素,再创建多个子进程,每个子进程通过worker_entry处理文件。等待子进程结束。
关键代码
读写buffer
struct task get_item()
{
struct task item;
pthread_mutex_lock(&mutex);
while(buffer_is_empty())
{
printf("wait_full\n");
pthread_cond_wait(&wait_full_buffer,&mutex);
}
item = buffer[out];
out = (out+1)% CAPACITY;
pthread_cond_signal(&wait_empty_buffer);
pthread_mutex_unlock(&mutex);
return item;
}
void put_item(struct task item)
{
pthread_mutex_lock(&mutex);
while(buffer_is_full())
{
pthread_cond_wait(&wait_empty_buffer,&mutex);
}
buffer[in]=item;
in = (in+1)%CAPACITY;
pthread_cond_signal(&wait_full_buffer);
pthread_mutex_unlock(&mutex);
}
找文件,找到文件放入buffer
void find_dir(char *path,char *target)
{
DIR *dir = opendir(path);
struct dirent *entry;
struct task buffer;
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 base[80];
memset(base,'\0',sizeof(base));
strcpy(base,path);
strcat(base,"/");
strcat(base,entry->d_name);
// printf("base:%s\n",base);
find_dir(base,target);
}
if(entry->d_type == DT_REG)
{
char base[80];
memset(base,'\0',sizeof(base));
strcpy(base,path);
strcat(base,"/");
strcat(base,entry->d_name);
//join array
buffer.is_end=0;
strcpy(buffer.path,base);
strcpy(buffer.string,target);
put_item(buffer);
// printf("put_item\n");
break;
}
}
closedir(dir);
}
主函数,如果输入的是路径,找出所有文件,给buffer,创建子线程,查找含单词的行。
int main(int argc,char *argv[])
{
char *path = argv[1];
char *string = argv[2];
if(isfile(path))
{
find_file(path,string);
return 0;
}
pthread_t consumer_tid[WORKER_NUMBER];
find_dir(path,string);
struct task buf;
buf.is_end=1;
put_item(buf);
//create
for(int i=0;i<WORKER_NUMBER;i++)
{
pthread_create(&consumer_tid[i],NULL,worker_entry,NULL);
}
//is_end
//exit
for(int i=0;i<WORKER_NUMBER;i++)
{
pthread_join(consumer_tid[i],NULL);
}
return 0;
}
运行结果
指令:
./a.out test/world/world.c int
输出:
test/world/world.c: printf("%s: %s",path,line);
test/world/world.c: printf("dir %s\n",entry->d_name);
test/world/world.c: printf("file %s\n",entry->d_name);
test/world/world.c: int main(int argc,char *argv[])
指令:
./a.out test main
输出:
test/world/world.c: int main(int argc,char *argv[])
test/hello/hello.c: int main(int argc,char *argv[])
总结
在本学期的操作系统实验这门课学习当中,为了更好的了解操作系统相关知识,我们在Linux系统做了10个实验。在实验的过程中,我对课堂上学到的操作系统的一些知识有了新的认识,通过实验动手写shell让我深入了解了命令执行原理,通过代码实现了生产者-消费者加深了我对条件变量、信号量的理解,收获颇丰。