OS-Experiment for NUAA

    技术2022-07-11  98

    文章目录

    文件读写myechomycatmycp 多进程mysyssh3 多线程pi1pi2sortpc1pc2

    文件读写

    myecho

    1.实现功能

    myecho.c的功能与系统echo程序相同,接受命令行参数,并将参数打印出来。

    2.实现思路

    main函数有两个参数:argc和argv,其中argc记录了main 函数的命令行参数总个数,包括可执行程序名;argv[] 是一个字符串数组,每个元素指向一个参数,在命令行输入的字符串会被自动分割为字符串数组,其长度为argc,argv[0]=可执行程序名,argv[1…argc-1]=可执行程序参数,argv[argc] = NULL。

    利用main函数的两个参数可以实现echo功能:首先根据argc的值判断是否有需要打印的字符串,如果argc==1,说明只有可执行程序名,没有参数,直接输出 Character not received! 并返回;

    /* user haven't input any char */ if(argc == 1){ printf("Character not received!\n"); return 0; }

    如果不为0,输出 argv 字符串数组中的值即可。需要注意的是:argv[0]中存储的是可执行程序名称,不应该被输出,所以下标应从 1 开始:

    /* print char from argv */ for(int i = 1 ;i < argc ; i++) printf("%s ",argv[i]); printf("\n");

    mycat

    1.实现功能

    mycat.c的功能与系统cat程序相同,mycat将指定的文件内容输出到屏幕,可以处理无参数和多参数的情况,要求使用系统调用open/read/write/close实现。

    2.实现思路

    mycat在输出文件内容时主要通过函数调用read/write实现,read函数返回值为rd,如果读取失败,返回-1,如果读取成功,返回实际读取的字节个数;返回0则代表读取到了文件末尾,可以用if语句作为是否读完的条件判断,再将读到的rd个字节用write写出到标准输出 STDOUT_FILENO。

    mycat在没有参数时,也就是argc==1,只有一个程序名,那程序会将用户在屏幕输入的内容原封不动打印出来,此时read的文件描述符参数就是STDIN_FILENO,表示从屏幕输入。由于输入的内容大小不确定,因此需要开辟一个不是很大的缓冲区来接收字符,通过循环来不断读,这样可以避免输入过大或过小造成的缓冲区溢出或浪费:

    if(argc == 1) { while(1) { int rd; char buf[10]; if(rd = read(STDIN_FILENO,buf,MAX) > 0) write(STDOUT_FILENO,buf,rd); else break; } }

    mycat有多个参数时,会将这些文件依次打开并输出到标准输出,因此当 argc>1 时,对 argv 中的文件逐个打开,调用read 读取打开的文件,参数为fd,然后用 write 函数将当前文件的字符写到标准输出中去。同样,由于输入的内容大小不确定,因此需要开辟一个不是很大的缓冲区来接收字符,通过循环来不断读,这样可以避免输入过大或过小造成的缓冲区溢出或浪费:

    /* show multiple files */ for(int i = 1;i < argc;i++){ int fd = open(argv[i],O_RDONLY); if(fd < 0) panic("open file fail!"); while(1) { char buf[10]; memset(buf,0,MAX); int rd; if((rd = read(fd,buf,MAX)) > 0) /* write to screen */ write(STDOUT_FILENO,buf,rd); else break; } close(fd); }

    mycp

    1.实现功能

    mycp.c的功能与系统cp程序相同, 将源文件复制到目标文件,要求使用系统调用open/read/write/close实现

    2.实现思路

    对于mycp,和之前两个程序不同,mpcp需要有可执行程序名argv[0],源文件argv[1],目的文件argv[2],当它的参数小于3个的时候一定是参数错误,因此要先判断 if(argc < 3)

    if(argc != 3) panic("Parameter error!");

    对于目的文件,可能不存在,这时应该创建一个新的文件,因此open函数用 O_CREAT,mode 打开,赋予权限为0777,若两次执行程序都移动到同一文件中,需要覆盖操作,因此open函数还需 O_TRUNC 参数。当文件都打开/创建成功时,调用 read 函数对源文件逐个读取,调用 write 函数写入目标文件中。这里write的文件描述符不再是标准输出STDOUT,而是目标文件的fd2:

    int fd1 = open(argv[1], O_RDONLY); mode_t mode=0777; int fd2 = open(argv[2], O_RDWR | O_TRUNC | O_CREAT,mode); if(fd1 < 0 || fd2 < 0) panic("Open file 1 failed!"); char buf[MAX]; memset(buf,0,MAX); int rd; while(1) { rd = read(fd1,buf,MAX); if(rd > 0) write(fd2,buf,rd); else break; } close(fd1); close(fd2);

    多进程

    mysys

    1.实现功能

    mysys的功能与系统函数system相同,要求用进程管理相关系统调用自己实现一遍,使用fork/exec/wait系统调用。

    3.实现思路

    mysys的实现主要基于以下三个函数:execvp、strtok、fork

    程序main函数读入读入一个字符串 command,传给mysys模块,在mysys模块中,先检查命令长度是否为0,如果没有要执行的命令,则直接返回退出;定义一个myargc表示这个命令的参数个数,定义一个myargv表示参数列表,调用change模块进行字符串处理,返回值为myargc参数个数:

    int t=strlen(command); /* if no command, continue to exec the next one */ if(t == 0) return; int myargc; char *myargv[MAXARG]; myargc = change(command,myargc,myargv); myargv[myargc]=NULL;

    change模块:strtok不可以直接对字符串常量进行分割,要用strncpy得到一个char型数组buf, 对这个buf数组进行处理即可。用空格作为分隔符,首次调用时,第一个参数指向要分解的字符串,之后再次调用要把第一个参数设成NULL,将分割后的子字符串存入 myargv字符数组中,每次myargc++:

    int change(char *command,int myargc,char *myargv[]) { char buf[MAX]; memset(buf,0,MAX); strncpy(buf,command,strlen(command)); char *delim=" "; myargc=0; myargv[myargc]=strtok(buf,delim); while(myargv[++myargc]=strtok(NULL,delim)); return myargc; }

    分割字符串成功后就开始创建一个子进程,调用子进程执行模块chile,让子进程执行execvp,父进程等待子进程结束再退出:

    pid_t pid; pid = fork(); if(pid == 0) child(myargc,myargv); else wait(NULL);

    子进程模块调用execvp,第一个参数为可执行程序名,存在了myargv[0]中,之后的参数列表通过myargv传入,装入可执行程序,执行命令:

    void child(int myargc,char *myargv[]) { int status = execvp(myargv[0],myargv); if(status < 0) Perror("Arguments fault!"); exit(1); }

    sh3

    1.实现功能

    实现管道和文件重定向。

    2.实现构图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RfaqS1mh-1593603294451)(C:\Users\lenovo\Desktop\2.PNG)]

    3.实现思路

    main函数中调用mysys模块:

    int main() { while (1) mysys(); return 0; }

    mysys模块实现的是内置命令的处理,其他的命令交给parse_command模块。

    首先通过fgets读取字符串,并去掉最后一个换行符变为\0:

    char command[ARG_MAX + 1], argv[ARG_MAX + 1]; memset(command, 0, ARG_MAX + 1); printf("> "); fgets(command, ARG_MAX, stdin); command[strlen(command) - 1] = '\0';

    先把第一个空格之前的子串分离出来分类讨论

    如果是内置命令,就不用fork一个子进程,而是直接执行,exit直接调用exit(0)退出,cd调用chdir(path)切换路径;

    如果不是内置命令,就交给parse_command来分析命令:

    char *p; p = command; /* first command */ char temp[10]; memset(temp,0,10); int i=0,j=0; while((*p!=' ')&& (*p)) { temp[i++]=command[j++]; ++p; } if(strcmp(temp,"exit") == 0) exit(0); else if(strcmp(temp,"cd") == 0) { ++p; int state = chdir(p); char buf[ARG_MAX]; memset(buf,0,ARG_MAX); printf("now in : %s\n",getcwd(buf,ARG_MAX)); } else { p=command; parse_commands(p); }

    parse_command模块中,用 | 作为分隔符,解析成子命令,并将子命令的个数记录在 cmd_count中,子命令名记录在cmds[]中,以便之后的执行。用一个for循环遍历这个command字符串。

    以 cat <input.txt | sort | uniq | cat >output.txt为例来说明这个过程:

    在最开始,把第一个原始命令加入cmds,并把cmd_count+1。当遍历到command[i] == '|' 时,让指针p指向 ‘|’ 前一个字符,去掉多余的空格之后,用\0来截断,分离出第一个 ‘|’左边的部分,这时command命令就变成了cat <input.txt:

    cmds[cmd_count++] = command; int len = strlen(command); for (i = 0; i < len; ++i) { if (command[i] == '|') { /* deal left and remove space */ p = command + i - 1; while (*p && (*p == ' ')) { *p = '\0'; --p; } ......

    再将指针p定位到 ‘|’ 右边第一个字符的位置,去掉多于的空格后,p就指向了 sort | uniq | cat >output.txt这一字符串,写进cmds,并把cmd_count+1:

    ...... /* deal right and remove space */ p = command + i + 1; while (*p && (*p == ' ')) { *p = '\0'; ++p; } /* cmd add one */ cmds[cmd_count++] = p; ......

    继续执行循环,当再次遇到 ‘|’ 时,同样将p指向’|'之前的一个字符,由于cmds[cmd_count++]是指向p的,去掉空格并用\0截断之后,cmd[1]就会自动变成sort,p再次指向unique| cat >output.txt,并把unique| cat >output.txt写进cmds,cmd_count+1…执行完这个循环后,cmd_count=4,

    cmd[0]=cat <input.txt

    cmd[1]=sort

    cmd[2]=uniq

    cmd[3]= cat >output.txt

    当然,这只是针对这一个特例的情况。要想实现所有类型的命令,就要先判断cmd_count的值,根据不同的count交给split_file模块进行不同的处理。

    先提前对于split_file模块进行说明,split_file模块的功能是解析重定向命令。

    如存在文件重定向,把它们分离出来记录到char *file_input, char *file_output变量中并打开,并且使用文件描述符;否则使用管道描述符。

    函数原型为:void split_file(char *command, int pipe_read, int pipe_write)

    command是上一步parse_command的分析结果,pipe_read和pipe_write是读描述符和写描述符,可以有也可以没有,没有某一个描述符时用-1表示。

    先定义一些变量:

    int fd_read = -1, fd_write = -1; int i, len = strlen(command); int flagout = 0, flagin = 0; char *file_input, *file_output;

    fd_read和fd_write或是一个打开的文件的描述符,或是一个管道的读端或写端,或两者都不是(-1);flagin/flagout用来标识有无’<’、’>’;file_input/file_output存储文件名。

    命令从头开始遍历,遇到’>‘时,说明需要重定向输出到指定的文件,用一个指针p指向’>'后的第一个字符,去掉多于空格后向后减,将command截断,用一个指针s去掉多余空格后指向输出文件,赋给file_output,并去掉多于的换行符等。

    例如对cmd[3]即cat >output.txt处理完后,command变成cat,file_output变成output.txt,最后将flagout置位1,表明该命令中有需要重定向输出的文件。

    for (i = 0; i < len; ++i) {'' /* find dis file */ if (command[i] == '>') { p = command + i - 1; while (*p == ' ') *(p--) = '\0'; command[i] = '\0'; char *s = command +i+1; while (*s == ' ') ++s; file_output = s; for (int j = 0; j < strlen(s); ++j) if (s[j] == ' ' || s[j] == '\n') s[j] = '\0'; flagout = 1; } ......

    遇到 ‘<’ 的情况类似:例如对cat <input.txt进行处理完后,command变为cat,file_input变为input.txt,并把flagin置1,表明有重定向输入的文件。

    ...... /* find src file */ else if (command[i] == '<') { char *s = command + i + 1; while (*s == ' ') ++s; file_input = s; for (int j = 0; j < strlen(s); ++j) if (s[j] == ' ' || s[j] == '\n') s[j] = '\0'; p = command + i - 1; while (*p == ' ') *(p--) = '\0'; flagin = 1; }

    之后对fd_read/fd_write进行赋值,如果flagin/flagout有效,表明这条命令中存在文件重定向,就打开它并作为该文件的描述符;

    如果flagin/flagout无效,但是pipe_read/pipe_write有效,说明这是一条管道命令,那么把这个管道描述符赋给fd_read/fd_write;

    如果二者皆无,说明只是一条普通命令,当做exec_simple处理,分割好输入输出文件之后,就交给exec_pipe模块。

    再回到parse_command模块中,该模块剩下的部分就是根据不同的cmd_count进行不同情况的分割文件,如果cmd_count == 1,说明不是一条管道命令,就向split_file传参数(-1,-1)表示没有管道:

    /* not a pipe command */ if (cmd_count == 1) { split_file(cmds[0], -1,-1); return; }

    如果包含管道命令就创建一个管道,并返回读端和写段,从cmd[0]开始遍历,由于执行第一个子命令时,只有后面有管道,所以用不到read,将对应参数置位-1即可,即

    split_file(cmds[i], -1, fd_pipe[1]);

    执行最后一个子命令时只有前面有管道,所以用不到write,将对应参数位置置位-1,即

    split_file(cmds[i], fd_pipe[0], -1);

    注意,对于非头尾的命令,可能前面有管道,后面也有管道,某一个子命令对应的子进程会用了两个管道,那么要在进入下一个子进程之前,抛弃老管道写端,建立新管道写端,即

    split_file(cmds[i], fd_tmp, fd_pipe[1]);

    将写描述符与上一条命令产生的的读描述符传入重定向解析函数:

    /* pipe command */ pipe(fd_pipe); for (i = 0; i < cmd_count; ++i) { if (i == 0) /* input to pipe,close fd[0] */ split_file(cmds[i], -1, fd_pipe[1]); else if (i != cmd_count - 1) { fd_tmp = fd_pipe[0]; status = pipe(fd_pipe); if (fd_tmp == -1) { printf("can't open pipe!\n"); return; } split_file(cmds[i], fd_tmp, fd_pipe[1]); } else /* output from pipe,close fd[1] */ split_file(cmds[i], fd_pipe[0], -1); }

    至此,parse_command对于’|'的处理结束,调用split_file分类处理完并拿到重定向输入输出文件描述符后,就会转向exec_pipe模块,这一模块实现创建子进程执行命令。首先创建子进程,父进程什么都不做,关闭不必要的读写端,等待子进程结束,为下一个子进程做准备:

    pid_t pid; pid = fork(); if (pid != 0) { if (fd_read != -1) close(fd_read); if (fd_write != -1) close(fd_write); wait(NULL); }

    每一个子命令对应一个子进程,fd_read != -1,说明需要重定向,或将标准输出重定向到输入文件,或将标准输出重定向到管道的读端;

    fd_write != -1,说明需要重定向,或将标准输出重定向到输出文件,或将标准输出重定向到管道的写端;

    例如当命令为cat /etc/passwd wc | -l时,会将标准输入重定向到管道的入口,将cat /etc/passwd写进管道,然后执行wc | -l 对管道内容操作。

    重定向处理完成后,就可以进行execvp装入可执行程序了:

    else { if (fd_read != -1) { close(STDIN_FILENO); status = dup2(fd_read, STDIN_FILENO); close(fd_read); if (status < 0) printf("%s dup stdin failed!\n", argv[0]); } if (fd_write != -1) { close(STDOUT_FILENO); status = dup2(fd_write, STDOUT_FILENO); close(fd_write); if (status < 0) printf("%s dup stdout failed!\n", argv[0]); } argc=change(command, argc, argv); argv[argc] = NULL; if (execvp(argv[0], argv) == -1) printf("%s exec failed!\n", argv[0]); exit(0); }

    多线程

    pi1

    1.实现功能

    莱布尼兹级数公式: 1 - 1/3 + 1/5 - 1/7 + 1/9 - … = PI/4,主线程创建1个辅助线程,主线程计算级数的前半部分,辅助线程计算级数的后半部分,主线程等待辅助线程运行结束后,将前半部分和后半部分相加。

    2.实现思路

    主线程创建一个子线程,让它从compute处开始执行,计算级数前半部分

    pthread_t tid; int state = pthread_create(&tid,NULL,&compute,NULL); if(state != 0) { printf("create error\n"); return 0; }

    compute计算部分无参数,迭代次数越大,结果越精确,这里accy=1e8,前半段计算到1e4,注意类型转化:

    void * compute(void * arg) { int N=accy/2; for(int i = 1;i < N;i = i + 2) { double tmp = (double)1/i; if(((i+1)/2)%2 == 0) tmp = -tmp; worker_output += tmp; } }

    主线程几乎做一样的步骤,只不过是从1e4计算到accy:

    int N = accy/2; for(int i = N;i <= accy;i = i+2) { double tmp = (double)1/i; if(((i+1)/2)%2 == 0) tmp = -tmp; master_output += tmp; }

    为了防止主线程提前退出,因此要等待子线程完成,再将前半部分和后半部分的值合并,输出:

    pthread_join(tid, NULL); sum = (worker_output+master_output); printf("Leibniz series = %.15lf\n",sum); printf("PI = %.15lf\n",sum*4);

    pi2

    1.实现功能

    与上一题类似,但本题更加通用化,能适应N个核心,主线程创建N个辅助线程,每个辅助线程计算一部分任务,并将结果返回。主线程等待N个辅助线程运行结束,将所有辅助线程的结果累加

    本题要求 1: 使用线程参数,消除程序中的代码重复

    本题要求 2: 不能使用全局变量存储线程返回值

    2.实现思路

    定义NR_TOTAL表明全部计算量,NR_CPU表示内核数目,NR_CHILD表明每个核的计算量:

    #define NR_TOTAL 100000000 #define NR_CPU 8 #define NR_CHILD (NR_TOTAL/NR_CPU)

    定义线程参数,记录每个核计算部分的开始和起始位置以及计算结果:

    struct param{ int start; int end; }; struct result{ double res=0.0; };

    主线程创建NR_CPU个线程,并为每一个线程分配任务,把总任务分成等量的NR_CPU份,启动每一个线程,并把线程参数传给compute:

    pthread_t tid[NR_CPU]; struct param params[NR_CPU]; for( int i = 0;i < NR_CPU;i++) { struct param *param; param = &params[i]; param->start = i * NR_CHILD; param->end = (i + 1) * NR_CHILD; int state = pthread_create(&tid[i], NULL, compute, param); if(state != 0) { printf("create error!\n"); return 0; } }

    compute计算部分一定要将接收到的参数进行强制类型转换,计算的结果要保存在result结构体中,保存之前要为它开辟空间,否则没有空间存储结果,最后将result结果返回:

    void * compute (void * arg) { struct param *param; struct result *result; double sum = 0.0; param = (struct param *)arg; for (int i = param->start; i < param->end; i++) { if(i&1==1) { double tmp=(double)1/i; if(((i+1)/2)%2 == 0) tmp = -tmp; sum +=tmp; } } result = malloc(sizeof(struct result)); result->res = sum; return result; }

    主线程不做任何计算,只负责分配任务,等所有子线程结束后,调用 pthread_join汇总结果,并释放result占用的空间,打印输出:

    double sum = 0.0; for (int i = 0; i < NR_CPU; i++) { struct result *result; pthread_join(tid[i], (void **)&result); sum += result->res; free(result); } printf("Leibniz series = %.15lf\n",sum); printf("PI = %.15lf\n", sum*4);

    sort

    1.实现功能

    主线程创建两个辅助线程,辅助线程1使用选择排序算法对数组的前半部分排序,辅助线程2使用选择排序算法对数组的后半部分排序,主线程等待辅助线程运行结束后,使用归并排序算法归并子线程的计算结果

    本题要求 : 使用线程参数,消除程序中的代码重复

    2.实现思路

    定义NR_TOTAL表明全部计算量,NR_CPU表示内核数目,NR_CHILD表明每个核的计算量,依据题目要求,NR_CPU的数量为2,创建两个子线程,数组元素个数为20:

    #define NR_TOTAL 20 #define NR_CPU 2 #define NR_CHILD (NR_TOTAL/NR_CPU)

    用rand函数产生随机数组:

    /* Randomly generated array */ for(int i = 0;i<NR_TOTAL;i++) { array[i]=(rand() % N); printf("%d ",array[i]); }

    主线程为这两个线程分配任务,启动每一个线程,并把线程参数传给compute:

    for (i = 0; i < NR_CPU; i++) { struct param *param; param = &params[i]; param->array = array; param->start = i * NR_CHILD; param->end = (i + 1) * NR_CHILD; /* child thread do select sort */ pthread_create(&workers[i], NULL, compute, param); }

    这两个子线程的计算部分都调用了选择排序函数,只不过第一个线程计算数组前半段,第二个线程计算数组后半段,根据param的start和end值来标识:

    void *compute(void *arg) { struct param *param; int i; param = (struct param *)arg; select_sort(array,param->start,param->end); printf("\n"); } void select_sort(int A[],int start,int end) { for(int i = start; i < end; i++) { int k = i; for(int j = i; j < end; j++) if(A[j] < A[k]) k=j; int temp = A[i]; A[i] = A[k]; A[k] = temp; } }

    主线程要等待两个子线程排序完毕:

    for (i = 0; i < NR_CPU; i++) pthread_join(workers[i], NULL);

    主线程再归并数组的前半部分和后半部分,直接在主线程里调用自定义的Merge函数即可将两个选择排序的结 果归并排序完成:

    /* parent thread do merge sort */ merge_sort(array,0,NR_TOTAL-1); printf("After sort : \n"); for (int i=0;i<NR_TOTAL;i++) printf("%d ",array[i]);

    pc1

    1.实现功能

    系统中有3个线程:生产者、计算者、消费者,系统中有2个容量为4的缓冲区:buffer1、buffer2,生产者生产’a’、‘b’、‘c’、‘d’、‘e’、‘f’、‘g’、'h’八个字符,放入到buffer1,计算者从buffer1取出字符,将小写字符转换为大写字符,放入到buffer2,消费者从buffer2取出字符,将其打印到屏幕上。

    3.实现思路

    使用长度为4的两个数组表示共享缓冲区,生产者生产八个字符,放入到buffer1,计算者从 buffer1 取出字符,将小写字符转换为大写字符,放入到 buffer2,消费者从 buffer2 取出字符,将其打印到屏幕上;变量out1,out2分别为两个共享缓冲区的读指针,变量in1,in2分别为两个共享缓冲区的写指针:

    #define CAPACITY 4 char buffer1[CAPACITY]; char buffer2[CAPACITY]; int in1=0,in2=0; int out1=0,out2=0;

    由于有两个共享缓冲区,所以要用flag来标识是1号还是2号,in指针和out指针相同时,缓冲区为空;n指针和out指针相邻时,缓冲区为满,用求模保证数组不越界:

    int buffer_is_empty(int flag) { if(flag == 1) return in1 == out1; if(flag == 2) return in2 == out2; } int buffer_is_full(int flag) { if(flag == 1) return (in1 + 1) % CAPACITY == out1; if(flag == 2) return (in2 + 1) % CAPACITY == out2; }

    对缓冲区的生产/消费动作要根据flag判断是对哪个缓冲区操作,get_item获取out指针指向的元素同时,移动out指针指向下一项;put_item将元素放置在in指针指向的位置同时,移动in指针指向下一项:

    char get_item(int flag) { char item; if(flag == 1){ item = buffer1[out1]; out1 = (out1 + 1) % CAPACITY; } if(flag == 2){ item = buffer2[out2]; out2 = (out2 + 1) % CAPACITY; } return item; } void put_item(char item, int flag) { if(flag == 1){ buffer1[in1] = item; in1 = (in1 + 1) % CAPACITY; } if(flag == 2){ buffer2[in2] = item; in2 = (in2 + 1) % CAPACITY; } }

    定义互斥信号量mutex用于进程间互斥,定义条件变量pthread_cond_t用于进程间同步:

    pthread_mutex_t mutex1,mutex2; pthread_cond_t wait_empty_buffer1,wait_empty_buffer2; pthread_cond_t wait_full_buffer1,wait_full_buffer2;

    生产者的任务是生产八个字符,放入到buffer1,为了实现互斥,在对缓冲区操作前必须加锁,为了避免缓冲区1满的情况,要先试探是否还有空间,再选择等待还是继续执行。生产结束后,释放一个满缓冲区,并解锁以唤醒计算者:

    void *produce(void *arg) { char item; for(int i = 0;i < ITEM_COUNT;i++){ pthread_mutex_lock(&mutex1); /* producer use the first buffer */ while(buffer_is_full(1)) pthread_cond_wait(&wait_empty_buffer1, &mutex1); item = 'a' + i; put_item(item,1); printf("produce item:%c\n",item); pthread_cond_signal(&wait_full_buffer1); pthread_mutex_unlock(&mutex1); } return NULL; }

    计算者从buffer1取出字符,将小写字符转换为大写字符,放入到buffer2,先作为 buffer1 的消费者,给 buffer1 加锁并取数,将小写字母变成大写字母;最后再作为buffer2 的生产者,给 buffer2 加锁并存数,完成对两个缓冲区的存取:

    void *compute(void *arg) { char item; for(int i = 0;i < ITEM_COUNT;i++) { pthread_mutex_lock(&mutex1); /* computer get item from buf1 */ while(buffer_is_empty(1)) pthread_cond_wait(&wait_full_buffer1, &mutex1); item = get_item(1); pthread_cond_signal(&wait_empty_buffer1); pthread_mutex_unlock(&mutex1); /* change to capital */ item -= 32; pthread_mutex_lock(&mutex2); /* and put in to buf2 */ while(buffer_is_full(2)) pthread_cond_wait(&wait_empty_buffer2, &mutex2); put_item(item,2); printf(" compute put item:%c\n", item); pthread_cond_signal(&wait_full_buffer2); pthread_mutex_unlock(&mutex2); } return NULL; }

    消费者作为 buffer2 的消费者,给 buffer2 加锁并取数字,取完后要释放一个空缓冲区并解锁唤醒计算者:

    void *consume(void *arg) { char item; for(int i = 0;i < ITEM_COUNT;i++){ pthread_mutex_lock(&mutex2); /* consumer get item ,use the second buffer */ while(buffer_is_empty(2)) pthread_cond_wait(&wait_full_buffer2, &mutex2); item = get_item(2); printf(" comsume item:%c\n", item); pthread_cond_signal(&wait_empty_buffer2); pthread_mutex_unlock(&mutex2); } return NULL; }

    在主线程中创建三个线程,分别用于承担生产者,计算者与消费者:

    pthread_t tids[3]; pthread_create(&tids[0],NULL,produce,NULL); pthread_create(&tids[1],NULL,compute,NULL); pthread_create(&tids[2],NULL,consume,NULL);

    定义两个锁用于线程间互斥:

    pthread_mutex_init(&mutex1, NULL); pthread_mutex_init(&mutex2, 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_join()函数等待线程结束:

    for(int i = 0;i < 3;i++) pthread_join(tids[i],NULL);

    pc2

    1.实现功能

    功能和前面的实验相同,使用信号量解决

    2.实现思路

    实现的时候利用信号量,使用条件变量实现信号量sema_t,value记录了信号量的值

    typedef struct{ int value; pthread_mutex_t mutex; pthread_cond_t cond; }sema_t;

    如果信号量的值小于等于0,则等待条件变量,将信号量的值减一:

    void sema_wait(sema_t *sema) { pthread_mutex_lock(&sema->mutex); while(sema->value <= 0) pthread_cond_wait(&sema->cond, &sema->mutex); sema->value--; pthread_mutex_unlock(&sema->mutex); }

    对共享缓冲区的操作结束后,将信号量的值加一,唤醒等待条件变量的线程:

    void sema_signal(sema_t *sema) { pthread_mutex_lock(&sema->mutex); ++sema->value; pthread_cond_signal(&sema->cond); pthread_mutex_unlock(&sema->mutex); }

    定义两个信号量 mutex_sema1,mutex_sema2,分别对(生产者-计算者)与(计算者-消费者)进行线程间互斥:

    sema_t mutex_sema1,mutex_sema2;

    定义了四个信号量 对共享变量 buffer1,buffer2 进行线程间同步。

    sema_t empty_buffer_sema1,empty_buffer_sema2; sema_t full_buffer_sema1,full_buffer_sema2;

    在生产者、计算者、消费者的函数中,先等待互斥信号量(上锁),再获取同步信号量,对 buffer 中的数据进行操作后,释放互斥信号量及解锁。要注意,需要先获取同步信号量再对互斥信号量进行上锁,不然可能造饥饿的现象:

    void *consume(void *arg){ char item; for(int i = 0;i < ITEM_COUNT;i++) { /* consumer get item ,use the second buffer */ sema_wait(&full_buffer_sema2); sema_wait(&mutex_sema2); item = get_item(2); printf(" comsume item:%c\n", item); sema_signal(&mutex_sema2); sema_signal(&empty_buffer_sema2); } return NULL; } void *produce(void *arg){ char item; for(int i = 0;i < ITEM_COUNT;i++) { /* producer use the first buffer */ sema_wait(&empty_buffer_sema1); sema_wait(&mutex_sema1); item = 'a' + i; put_item(item,1); printf("produce item:%c\n",item); sema_signal(&mutex_sema1); sema_signal(&full_buffer_sema1); } return NULL; } void *compute(void *arg){ char item; for(int i = 0;i < ITEM_COUNT;i++) { /* computer get item from buf1 */ sema_wait(&full_buffer_sema1); sema_wait(&mutex_sema1); item = get_item(1); sema_signal(&mutex_sema1); sema_signal(&empty_buffer_sema1); /* change to capital */ item -= 32; sema_wait(&empty_buffer_sema2); sema_wait(&mutex_sema2); /* and put in to buf2 */ put_item(item,2); printf(" compute put item:%c\n", item); sema_signal(&mutex_sema2); sema_signal(&full_buffer_sema2); } return NULL; }

    main 函数中开启三个线程分别对应生产者、计算者、消费者,再对两个互斥信号量以及四个同步信号量进行初始化,调用 pthread_join 函数等待三个进程的结束即可:

    /* create three thread */ pthread_t tids[3]; sema_init(&mutex_sema1, 1); sema_init(&mutex_sema2, 1); sema_init(&empty_buffer_sema1,CAPACITY - 1); sema_init(&full_buffer_sema1,0); sema_init(&empty_buffer_sema2,CAPACITY - 1); sema_init(&full_buffer_sema1,0); pthread_create(&tids[0],NULL,produce,NULL); pthread_create(&tids[1],NULL,compute,NULL); pthread_create(&tids[2],NULL,consume,NULL); for(int i = 0;i < 3;i++) pthread_join(tids[i],NULL);

    ll_buffer_sema1); sema_wait(&mutex_sema1);

    item = get_item(1); sema_signal(&mutex_sema1); sema_signal(&empty_buffer_sema1); /* change to capital */ item -= 32; sema_wait(&empty_buffer_sema2); sema_wait(&mutex_sema2); /* and put in to buf2 */ put_item(item,2); printf(" compute put item:%c\n", item); sema_signal(&mutex_sema2); sema_signal(&full_buffer_sema2); } return NULL;

    }

    main 函数中开启三个线程分别对应生产者、计算者、消费者,再对两个互斥信号量以及四个同步信号量进行初始化,调用 pthread_join 函数等待三个进程的结束即可: ```c /* create three thread */ pthread_t tids[3]; sema_init(&mutex_sema1, 1); sema_init(&mutex_sema2, 1); sema_init(&empty_buffer_sema1,CAPACITY - 1); sema_init(&full_buffer_sema1,0); sema_init(&empty_buffer_sema2,CAPACITY - 1); sema_init(&full_buffer_sema1,0); pthread_create(&tids[0],NULL,produce,NULL); pthread_create(&tids[1],NULL,compute,NULL); pthread_create(&tids[2],NULL,consume,NULL); for(int i = 0;i < 3;i++) pthread_join(tids[i],NULL);

    需源码可滴滴

    Processed: 0.011, SQL: 9