一、题目二
实验二 Linux 进程管理
二、实验目的
通过进程的创建、撤销和运行加深对进程概念和进程并发执行的理解,明确进程和程序之间的区别。
三、总体设计
1.背景知识
在 Linux 中创建子进程要使用 fork()函数,执行新的命令要使用 exec()系列函数,等待子进 程结束使用 wait()函数,结束终止进程使用 exit()函数。fork()原型如下:pid_t fork(void);fork 建立一个子进程,父进程继续运行,子进程在同样的位置执行同样的程序。对于父进程,fork()返回子进程的 pid, 对于子进程,fork()返回 0。出错时返回-1。
2.模块介绍
2-1:一个父进程,两个子进程
2-2:一个父进程,一个子进程
2-3:一个父进程,多个子进程
3.设计步骤
(1)进程的创建
任务要求:编写一段程序,使用系统调用 fork()创建两个子进程。当此程序运行时,在系统 中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”; 两子进程分别显示字符“b”和字符“c”。
步骤 1:使用 vi 或 gedit 新建一个 fork_demo.c 程序,然后拷贝清单 2-1 中的程序,使用 cc 或者gcc 编译成可执行文件 fork_demo。例如,可以使用 gcc –o fork_demo fork_demo.c 完成编译。
步骤 2:在命令行输入./fork_demo 运行该程序。
图2-1 进程的创建输出结果
(2)子进程执行新任务
任务要求:编写一段程序,使用系统调用 fork()创建一个子进程。子进程通过系统调用 exec 更换自己原有的执行代码,转去执行 Linux 命令/bin/ls (显示当前目录的列表),然后调用 exit()函 数结束。父进程则调用 waitpid()等待子进程结束,并在子进程结束后显示子进程的标识符,然后正 常结束。程序执行过程如图 2-1 所示。
步骤 1:使用 vi 或 gedit 新建一个 exec_demo.c 程序,然后拷贝清单 2-2 中的程序(该程序的执 行如图 2-1 所示),使用 cc 或者 gcc 编译成可执行文件 exec_demo。例如,可以使用 gcc –o exec_demo exec_demo.c 完成编译。
步骤 2:在命令行输入./exec_demo 运行该程序。
步骤 3:观察该程序在屏幕上的显示结果,并分析。
图2-2 子进程执行新任务输出结果
(3)实现一个简单的 shell(命令行解释器) (此任务有一些难度,可选做)。
任务要求:要设计的 shell 类似于 sh,bash,csh 等,必须支持以下内部命令:
cd <目录>更改当前的工作目录到另一个<目录>。如果<目录>未指定,输出当前工作目录。如
果<目录>不存在,应当有适当的错误信息提示。这个命令应该也能改变 PWD 的环境变量。
environ 列出所有环境变量字符串的设置(类似于 Unix 系统下的 env 命令)。
echo <内容 > 显示 echo 后的内容且换行
help 简短概要的输出你的 shell 的使用方法和基本功能。
jobs 输出 shell 当前的一系列子进程,必须提供子进程的命名和 PID 号。
quit,exit,bye 退出 shell。
图2-3 实现一个简单的 shell输出结果
四、详细设计
- 数据结构
一个进程创建多个子进程时,则子进程之间具有兄弟关系,数据结构为链表结构,也运用了一些C++库函数。 - 程序流程图
图2-4 进程的创建流程图
图2-5 子进程执行新任务流程图
图2-6 实现一个简单的 shell(命令行解释器)流程图
3. 关键代码
2-1 创建进程
#include <sys/types.h> #include <stdio.h> #include <unistd.h> int main () { int x; while((x=fork())==-1); if (x==0){ x=fork(); if(x>0) printf("b"); else printf("c"); } else printf("a"); }
2-2 子进程执行新任务
#include <sys/types.h> #include <stdio.h> #include <unistd.h> int main() { pid_t pid; /* fork a child process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed"); return 1; } else if (pid == 0) { /* 子进程 */ execlp("/bin/ls","ls",NULL); } else { /* 父进程 */ /* 父进程将一直等待,直到子进程运行完毕*/ wait(NULL); printf("Child Complete"); } return 0; } } return 0; }
2-3 实现一个简单的 shell(命令行解释器) (选做)
#include<stdio.h> #include<string.h> #include<sys/types.h> #include<unistd.h> int main() { char cmd[666]; char cata[100]; while(1) { int len,i,flag,cnt; printf("Enter commands:"); // print String scanf("%s",cmd); // Calculation String len = strlen(cmd); // for cd if(cmd[0]=='c') { flag=0; cnt=0; // Start after command for(i=3; i<len-1; i++) { // String is not null if(cmd[i]!=' ') flag=1; if(flag) { cata[cnt++] = cmd[i]; } } // String is null if(cnt==0) { printf("path error!\n"); cata[0]='.'; cata[1]='\0'; } } //for echo if(cmd[0]=='e'&&cmd[1]=='c') { flag = 0; for(i=5; i<len-1; i++) { if(cmd[i]!=' ') flag=1; if(flag) { putchar(cmd[i]); } } if(flag) putchar('\n'); } // for help if(cmd[0]=='h') { printf("/**********Method***********/\n"); printf("print cd<catalog> :find directory\n"); printf("print environ :List set\n"); printf("print echo<content> : print content\n"); printf("print help :List Method\n"); printf("print jobs :provide PID\n"); printf("print quit,exit,bye :break \n"); printf("/******Method***************/\n"); } // for quit,exit,bye if(cmd[0]=='q'||cmd[1]=='x'||cmd[0]=='b') { printf("break\n"); return 0; } else { cnt=0; // child process pid_t pid = fork(); if(pid<0) { // error occurred fprintf(stderr,"Fork Failed" ); return 1; } else if(pid==0) { //cd if(cmd[0]=='c') { execlp("/bin/ls",cata,NULL); } //jobs else if(cmd[0]=='j') { execlp("pstree","-p",NULL); } //environ else if(cmd[1]=='n') { execlp("env","",NULL); } } else { //wait child process exit wait(); } } printf("\n"); } return 0; }
五、实验结果与分析
实验2-1结果分析:修改后代码清单2-1后,从main()函数开始,运行父进程,通过while((x=fork())== -1)判断创建进程是否成功,如果x>0,则继续创建子进程,若成功,则此时有两个子进程和一个父进程,先创建的子进程会输出c,接下来是父进程执行完毕,输出a,后面是后创建的子进程执行完毕输出b;所以最终的输出结果是abc。
实验2-2结果分析:从main()函数开始,父进程创建子进程,首先判断子进程是否创建成功,如果pid<0则创建进程失败,当pid=0时,运行子进程,输出系统当前目录。父进程将会一直等待子进程信号,只有当子进程释放信号,父进程输出“Child Complete”。
实验2-3结果分析:从main()函数开始,根据下面这些关键字的标志位进行设置判断,然后再在判断之后对下面的功能进行实现:cd <目录>更改当前的工作目录到另一个<目录>。如果<目录>未指定,输出当前工作目录。如果<目录>不存在,应当有适当的错误信息提示。这个命令应该也能改变 PWD 的环境变量。environ 列出所有环境变量字符串的设置(类似于Unix 系统下的 env 命令)。echo <内容 > 显示 echo 后的内容且换行help 简短概要的输出你的 shell 的使用方法和基本功能。jobs 输出 shell 当前的一系列子进程,必须提供子进程的命名和 PID 号。quit,exit,bye 退出 shell,也就是依次终止运行的父子进程。
六、小结与心得体会
通过这个实验加深了我对Linux操作系统的进程概念的了解,也学会了在Linux基本运行,也使我明白了在Linux系统中子进程的创建,以及父子进程的运行过程,加深了对进程运行的理解。在Linux中利用fork建立一个子进程,父进程继续运行,子进程在同样的位置执行同样的程序。对于父进程,fork()返回子进程的 pid, 对于子进程,fork()返回 0,出错时返回-1,while((x=fork())==-1)这句话是用来判断子进程是否能创建成功,而且当x=0时运行子进程,当x>0时父进程执行,而x<0时,则进程创建不成功,通过代码确定父子进程的先后执行顺序。同时也完成实现一个简单的 shell(命令行解释器),这个是选做但我也挑战自己做这道题目,从中也收获非常多,采用了关键字这种思路去慢慢分块实现不同命令的功能,对于逻辑处理也提升很多。
一、题目三
实验三 互斥与同步
二、实验目的
(1) 回顾操作系统进程、线程的有关概念,加深对 Windows 线程的理解。
(2) 了解互斥体对象,利用互斥与同步操作编写生产者-消费者问题的并发程序,加深对 P (即semWait)、V(即 semSignal)原语以及利用 P、V 原语进行进程间同步与互斥操作的理解。
三、总体设计
1.基本原理与算法
1.1、利用的是互斥与同步中的信号量
1.2、使用信号量解决有限缓冲区生产者和消费者问题
2.模块介绍
主要有两大模块:生产者和消费者;生产者又包括Produce(),Append(); 消费者包括Take(),Consume(); 线程的创建。
3.设计步骤
(1) 生产者消费者问题
步骤 1:创建一个“Win32 Consol Application”工程,然后拷贝清单 3-1 中的程序,编译成可执行文件。
步骤 2:在“命令提示符”窗口运行步骤 1 中生成的可执行文件,列出运行结果。
步骤 3:仔细阅读源程序,找出创建线程的 WINDOWS API 函数,回答下列问题:线程的第一个执行函数是什么(从哪里开始执行)?它位于创建线程的 API 函数的第几个参数中?
答:Produce()函数,位于第三个参数。
步骤 4:修改清单 3-1 中的程序,调整生产者线程和消费者线程的个数,使得消费者数目大与生产者,看看结果有何不同。察看运行结果,从中你可以得出什么结论?
答:当生产者个数多于消费者个数时生产速度快,生产者经常等待消费者对产品进行消费;反之,消费者经常等待生产者生产。
步骤 5:修改清单 3-1 中的程序,按程序注释中的说明修改信号量 EmptySemaphore 的初始化方法,看看结果有何不同。
答:结果为空,因为参数设置成可用资源为0,所以进程无法使用。
步骤 6:根据步骤 4 的结果,并查看 MSDN,回答下列问题:
1)CreateMutex 中有几个参数,各代表什么含义。
2)CreateSemaphore 中有几个参数,各代表什么含义,信号量的初值在第几个参数中。
3)程序中 P、V 原语所对应的实际 Windows API 函数是什么,写出这几条语句。
4)CreateMutex 能用 CreateSemaphore 替代吗?尝试修改程序 3-1,将信号量 Mutex 完全用CreateSemaphore 及相关函数实现。写出要修改的语句。
答:
(1)3个;LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针BOOLbInitialOwner, // 初始化互斥对象的所有者;LPCTSTRlpName // 指向互斥对象名的指针;第二个参数是FALSE,表示刚刚创建的这个Mutex不属于任何线程。
(2)4个;//第一个参数:安全属性,如果为NULL则是默认安全属性 //第二个参数:信号量的初始值,要>=0且<=第三个参数 //第三个参数:信号量的最大值 //第四个参数:信号量的名称。
(3)WaitForSingleObject(FullSemaphore,INFINITE); P(full);
WaitForSingleObject(Mutex,INFINITE); //P(mutex);
ReleaseMutex(Mutex); //V(mutex);
ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty);
(4)可以,Mutex=CreateSemaphore(NULL,false,false,NULL);生产者,消费者内: ReleaseMutex(Mutex);改为 ReleaseSemaphore(Mutex,1,NULL)。
图3-1 生产者消费者问题输出结果
(2) 读者写者问题
根据实验(1)中所熟悉的 P、V 原语对应的实际 Windows API 函数,并参考教材中读者、写者问题的算法原理,尝试利用 Windows API 函数实现第一类读者写者问题(读者优先)。
图3-2 读者写者问题输出结果
四、详细设计
- 数据结构
应用了循环队列、数组,信号量。 - 程序流程图
图3-3 生产者消费者问题流程图
图3-4 读者写者问题流程图
3. 关键代码
3-1 创建进程
int main() { //创建各个互斥信号 //注意,互斥信号量和同步信号量的定义方法不同,互斥信号量调用的是 CreateMutex 函数,同步信号量调用的是 CreateSemaphore 函数,函数的返回值都是句柄。 Mutex = CreateMutex(NULL,FALSE,NULL); EmptySemaphore = CreateSemaphore(NULL,SIZE_OF_BUFFER,SIZE_OF_BUFFER,NULL); //将上句做如下修改,看看结果会怎样 // EmptySemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER-1,NULL); FullSemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER,NULL); //调整下面的数值,可以发现,当生产者个数多于消费者个数时, //生产速度快,生产者经常等待消费者;反之,消费者经常等待 const unsigned short PRODUCERS_COUNT = 10; //生产者的个数 const unsigned short CONSUMERS_COUNT = 1; //消费者的个数 //总的线程数 const unsigned short THREADS_COUNT = PRODUCERS_COUNT+CONSUMERS_COUNT; HANDLE hThreads[THREADS_COUNT]; //各线程的 handle DWORD producerID[PRODUCERS_COUNT]; //生产者线程的标识符 DWORD consumerID[CONSUMERS_COUNT]; //消费者线程的标识符 //创建生产者线程 for (int i=0; i<PRODUCERS_COUNT; ++i) { hThreads[i]=CreateThread(NULL,0,Producer,NULL,0,&producerID[i]); if (hThreads[i]==NULL) return -1; } //创建消费者线程 for (int i=0; i<CONSUMERS_COUNT; ++i) { hThreads[PRODUCERS_COUNT+i]=CreateThread(NULL,0,Consumer,NULL,0,&consumerID[i]); if (hThreads[i]==NULL) return -1; } while(p_ccontinue) { if(getchar()) //按回车后终止程序运行 { p_ccontinue = false; } } return 0; } //消费者 DWORD WINAPI Consumer(LPVOID lpPara) { while(p_ccontinue) { WaitForSingleObject(FullSemaphore,INFINITE); //P(full); WaitForSingleObject(Mutex,INFINITE); //P(mutex); Take(); Consume(); Sleep(1500); ReleaseMutex(Mutex); //V(mutex); ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty); } return 0; }
3-2 子进程执行新任务
int main() { Mutex = CreateMutex(NULL,FALSE,NULL); X = CreateMutex(NULL,FALSE,NULL); const unsigned short READERS_COUNT = 2;//创建两个读进程 const unsigned short WRITERS_COUNT = 1;//创建一个写进程 const unsigned short THREADS_COUNT = READERS_COUNT+WRITERS_COUNT; HANDLE hThreads[THREADS_COUNT]; //创建写线程 for (int i=0; i<WRITERS_COUNT; ++i) { hThreads[i]=CreateThread(NULL,0,writer,NULL,0,NULL); if (hThreads[i]==NULL) return -1; } //创建读线程 for (int i=0; i<READERS_COUNT; ++i) { hThreads[WRITERS_COUNT+i]=CreateThread(NULL,0,reader,NULL,0,NULL);//生产者线程函数Producer 线程ID&producerID[i] if (hThreads[i]==NULL) return -1; } //程序人为终止操作设计 while(p_ccontinue) { if(getchar()) //按回车后终止程序运行 { p_ccontinue = false; } } return 0; } //写者 DWORD WINAPI writer(LPVOID lpPara) { while(p_ccontinue) { WaitForSingleObject(Mutex,INFINITE); Write(); Sleep(1500); ReleaseMutex(Mutex); //V(mutex); } return 0; }
五、实验结果与分析
实验3-1结果分析:修改后代码清单3-1后,从main()函数开始,首先创建了生产者-消费者问题中应用到的互斥信号和同步信号以及其他基础定义,创建消费者生产者线程;最初生产者满足条件生产产品,所以先执行生产者,然后当资源有产品时,会执行消费者,生产者和消费者在代码运行过程中出现是随机的,当生产者多于消费者时,生产速度快,生产者经常等待消费者;反之,消费者经常等待;若缓冲区为空,则必定是生产者运行,缓冲区为满,则消费者运行,生产者等待,而对于结果的表示,则是调用了Append()和Consume()中的循环输出。
实验3-2结果分析:这个是读写者中读者优先的问题,从main()函数开始,首先创建了生产者-消费者问题中应用到的两个互斥信号以及其他基础定义,创建消读者写者线程;最初写者先创建先运行,然后会执行读者线程,由于设置了两个互斥信号量可以将其中一个作为读者优先设置信号量,当第一个读者拿到这个互斥信号量时,写者就得等待读者释放这个信号量,而其他读者就不用就直接拿到不用判断可以运行输出。对于结果的表示,也是调用了read ()和Write()函数进行输出。
六、小结与心得体会
通过这个实验,我更好的了解互斥体对象,利用互斥与同步操作编写生产者-消费者问题的并发程序,加深对 P (即 semWait)、V(即 semSignal)原语以及利用 P、V 原语进行进程间同步与互斥操作的理解,生产者消费者问题是一个典型的例题,主要涉及同步与互斥,这也保证了在程序运行过程中只能有一个线程进行。然后对于3-2问题,我借鉴了《操作系统》课程书籍中的读者优先的思路,并将其实现,在这个过程中收获非常多也非常大,对于信号量以及进程的了解也更加深刻。
以上只是操作系统课设部分设计内容,如果想要完整操作系统课设源代码资源有以下两种获取方式,请点击下面资源链接进行下载,希望能帮助到你!
操作系统课设完整资源:点击打开下载资源
操作系统课设完整资源:点击打开下载资源(注意:购买文章后,百度云盘链接大家不要直接复制链接,请手打链接否则可能打不开资源)