操作系统exp

1、linux系统(ubuntu)下运行C程序

1、touch XX.c生成源文件。

2、vim打开并编写代码。

3、gcc XX.c编译源文件,然后就可以看见a.out的文件。
(这个花了最多时间,安装build-essential包绕了很多弯路)

4、在a.out文件的目录下打开终端并输入./a.out运行。

5、如果想要编译完的文件名不要用a.out文件,在编译时输入gcc XX.c -o XX.out就可以看见一个XX.out文件了。-o后面跟着编译生成的文件名。

2、进程控制

fork():

使用时需包含头文件 <sys/types.h> 和 <unistd.h>。
函数原型: pid_t fork(void);

功能: 以父进程为模版创建子进程,它从父进程处继承了整个进程的地址空间:包括代码,数据段,进程上下文、进程堆栈、打开的文件描述符等。

返回值: 需要注意的是fork()函数被调用一次,却会返回两次。两次返回的区别是,子进程的返回值的是0,而父进程返回的是子进程的ID。若调用出错则返回-1。

看下面一个程序:

void fork1()
{
    int x = 1;
    pid_t pid = fork();
    if (pid == 0) {
	printf("Child has x = %d\n", ++x);
    } 
    else {
	printf("Parent has x = %d\n", --x);
    }
    printf("Bye from process %d with x = %d\n", getpid(), x);
}

当在Linux系统上运行该代码,我们会得到如下结果:
在这里插入图片描述
执行到fork()函数后,就创建了一个子进程,然后根据父进程和子进程返回值的不同,执行相应代码段。系统把原来的进程的所有代码,数据都复制到新进程中,所以子进程和父进程都会继续执行fork()调用之后的代码,一共打印了4次。(其中getpid()函数是返回调用进程的ID)

wait():

使用时需包含头文件<sys/types.h>和<sys/wait.h>
函数原型: int wait(int *status);

功能: 子进程退出,父进程如果不用wait()函数等待已终止的子进程,就可能造成“僵尸进程”(子进程无父进程的状态),进而造成内存泄露。父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

参数: status用来保存子进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何结束的毫不在意,只想把这个进程消灭掉,我们就可以设定这个参数为NULL。

返回值: 等待成功:返回子进程pid;等待失败:返回-1。

wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的pid。

waitpid():

头文件同上。
函数原型: pid_t waitpid(pid_t pid,int *status,int options)

参数:
pid: pid=-1,等待任意一个子进程,与wait等效;pid=0,等待进程id与目前进程相同的任何子进程;pid>0,等待进程id与pid相等的子进程。
status: 同上。
options: 提供一些额外的选项来控制waitpid,如果不想使用这些选项,则可以把这个参数设为0。
主要使用的有以下两个选项:

参数说明
WNOHANG如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。
WUNTRACED如果子进程进入暂停状态,则马上返回。

exec():

exec是一个函数簇,由6个函数组成,分别是以excl和execv打头的。

execl(const char* filepath,const char* arg1,char*arg2…)

execlp(const charfilename,const chararg1,const char*arg2… )

execle(const charfilepath,const chararg1,const chararg2,…,char cons envp[])

execv (const char* filepath,char* argv[])

execvp (const char* filename,char* argv[])

execve (const char* filepath,charargv[],char const envp[])

一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程id,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。

exit():

终止一个进程。传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般用-1或者1。

实验内容:

(1)编写一段程序,使用系统调用fork()来创建两个子进程,并由父进程重复显示某字符串和自己的标识数,而子进程则重复显示某字符串和自己的标识数。
程序如下:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main()
{
    if(fork()==0)
      printf("child1 ID1:%d\n",getpid());
 	else
 	{
      wait(NULL);
      printf("parent ID:%d\n",getpid());
      if(fork()==0)
        printf("child2 ID2:%d\n",getpid());
	  else
	  {
        wait(NULL);
        printf("parent ID:%d\n",getpid());
      }
    }
    return 0;
}

(2)编写一段程序,使用系统调用fork()来创建一个子进程。子进程通过系统调用exec()更换自己的执行代码,显示新的代码后,调用exit()结束。而父进程则调用waitpid()等待子进程结束,并在子进程结束后显示子进程的标识符,然后正常结束。
程序如下:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
 
int main()
{
    if(fork()==0)
    {
      execl("/bin/ls","ls",NULL);
      exit(0);
    }
    else
    {
       printf("parent=%d\n",getpid());
       printf("child=%d\n",waitpid(-1,NULL,0));
    }
 
    return 0;
 
}

3、进程间的通信

pipe():

创建管道。
函数原型: int pipe(int file_descriptor[2])
返回值:成功返回0,并通过值file_descriptor[2]传出两个文件描述符号。
file_descriptor[0] : 通过此文件描述符从管道读出数据
file_descriptor[1] : 通过此文件描述符向管道写数据

注:若关闭pipe的写入一端,则对于读取进程,read不会阻塞于等待数据,而是直接返回0

write()&read():

都位于<unistd.h>中。
函数原型:
ssize_t write(int fd,voidbuf,size_t count)
ssize_t read(int fd,void
buf,size_t count)
write()参数说明:
fd: 是文件描述符,对应1
buf: 需要写入的数据,通常为字符串
count: 每次写入的字节数
read()参数说明:
fd: 是文件描述符,对应0
buf: 为读出数据的缓冲区
count: 为每次读取的字节数

实验内容:

编写一段程序,使用管道来实现父子进程之间的进程通信。子进程向父进程发送自己的进程标识符,以及某字符串。父进程则通过管道读出子进程发来的消息,将消息显示在屏幕上,然后终止。或者,编写一段程序,使其用消息缓冲队列来实现client和server进程之间的通信。
程序如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
char msg[100];
int main() 
{
	int fd[2], pid;
	if (pipe(fd) == 0) 
	{
		pid = fork();
		if (pid == 0) 
		{
			sprintf(msg, "child_pid=%d\n", getpid());
			write(fd[1], msg, strlen(msg));
			sprintf(msg, "INTERPROCESS COMMUNICATION");
			write(fd[1], msg, strlen(msg));
		}
		else if (pid > 0)
		{
			read(fd[0], msg, sizeof(msg)); puts(msg);
			read(fd[0], msg, sizeof(msg)); puts(msg);
			wait(NULL);
		}
		else { puts("Fork Error."); exit(1); }
		return 0;
	}
	else exit(1);
}

4、使用动态优先权的进程调度算法模拟

动态优先级算法:

指在进程创建时先确定一个初始优先数, 以后在进程运行中随着进程特性的改变不断修改优先数,这样,由于开始优先数很低而得不到CPU的进程,就能因为等待时间的增长而优先数变为最高而得到CPU运行。

例如:在进程获得一次CPU后就将其优先数减少1(不一定是1),或者进程等待的时间超过某一时限时增加其优先数的值。

实验内容:

1.对N个进程采用动态优先权算法的进程调度。
2.每个用来标识进程的进程控制块PCB用结构来描述,包括以下字段:

  • 进程标识数 ID。
  • 进程优先数 priority,并规定优先数越大的进程,其优先权越高。
  • 进程已占用的CPU时间cputime。
  • 进程还需占用的CPU时间alltime。当进程运行完毕时,alltime变为0。
  • 进程的阻塞时间startblock,表示当进程再运行startblock个时间片后,将进入阻塞状态。
  • 进程被阻塞的时间blocktime,表示已足赛的进程再等待blocktime个时间片后,将转换成就绪状态。
  • 进程状态stare。
  • 队列指针next,用来将PCB排成队列。

3.优先数改变的原则:进程在就绪队列中呆一个时间片,优先数增加1,进程每运行一个时间片优先数减3。
4.设置调度前的初始状态。
5.将每个时间片内的进程情况显示出来。

程序如下:
priority= -1: 已执行完的进程或者已被阻塞的进程优先级设置为-1

temp[i]: 储存已阻塞的进程i的优先级

process[i].startblock= n : 表示进程再执行n个时间片开始阻塞;-1表示不会阻塞

#include <iostream>
#include<algorithm>             //使用库中 max_element(first,last)函数,函数返回值为数组最大元素的地址
using namespace std;
enum State{ready,block,done}; //枚举变量  0-ready   1-block    2-done
struct Process                  //进程结构体定义
{              
	int ID;
	int priority;
	int cputime;
	int alltime;
	int startblock;
	int blocktime;
	State state;
};
Process process[5]=
{
	{0,9,0,3,2,3,ready},
	{1,38,0,3,-1,0,ready},
	{2,30,0,6,-1,0,ready},
	{3,29,0,3,-1,0,ready},
	{4,0,0,4,-1,0,ready}
};                     //初始化5个进程
void print()
{
	cout<<"就绪队列:";
	for(int j=0; j<5; j++) 
	{
		if(process[j].state==ready) 
			cout<<"->"<<process[j].ID;
	}
	cout<<endl;
	cout<<"阻塞队列:";
	for(int j=0; j<5; j++)
	{
		if(process[j].state==block)
			cout<<"->"<<process[j].ID;
	}
	cout<<endl;
	cout<<"ID\tpriority\tcputime\talltime\tstartblock\tblocktime\tstate"<<endl;
	for(int j=0; j<5; j++) 
	{
		cout<<process[j].ID<<'\t'<<process[j].priority<<'\t'<<'\t'<<process[j].cputime<<'\t'<<process[j].alltime
			<<'\t'<<process[j].startblock<<'\t'<<'\t'<<process[j].blocktime<<'\t'<<'\t'<<process[j].state<<endl;
	}
	cout<<"******************************************************************************"<<endl;
}
int main()
{
	int timeslice=0;                    //时间片
	int temp[5];                       // 阻塞进程优先级  缓存数组
	while(process[0].alltime||process[1].alltime||process[2].alltime||process[3].alltime||process[4].alltime) //存在进程没有执行完成
	{ 
		for(int i=0; i<5; i++)
		{
			int array[]= {process[0].priority,process[1].priority,process[2].priority,process[3].priority,process[4].priority};  //所有进程优先级 存储数组
			if((process[i].state==ready) && (process[i].priority==*max_element(array,array+5)))	//进程i开始执行
			{               
				timeslice++;
				process[i].priority-=3;
				process[i].cputime+=1;
				process[i].alltime-=1;
				for(int j=0; j<5; j++)	//其余就绪进程优先级加1
				{                         
					if(j==i) continue;                    //跳过当前进程的优先级+1操作 
					if(process[j].alltime>0&&process[j].state==ready)
						process[j].priority+=1;
				}
				if(process[i].alltime==0)
				{
					process[i].state=done;
					process[i].priority=-1;
				}
				for(int j=0; j<5; j++)  //其他阻塞进程  blocktime  -1
				{                                        
					if(process[j].state==block&&process[j].blocktime>0) 
					{
						process[j].blocktime-=1;
						if(process[j].blocktime==0) 
						{
							process[j].state=ready;
							process[j].priority=temp[j];    //阻塞进程恢复阻塞前的优先级
						}
					}
				}
				if(process[i].startblock>0) 
				{
					process[i].startblock-=1;
					if(process[i].startblock==0) 
					{
						process[i].state=block;                       
						temp[i]=process[i].priority;        //进程i阻塞后   将优先级保存到 temp[i]
						process[i].priority=-1;
					}
				}
				cout<<"时间片为:"<<timeslice<<endl;
				cout<<"执行进程:"<<process[i].ID<<endl;
				print();
			}
		}
	}
	return 0;
}

5、动态分区分配方式

最先适应算法:

将空闲区按其在存储空间中的起始地址递增的顺序排列。为作业分配存储空间时,从空闲区链的始端开始查找,选择第一个满足要求的空闲区,而不管它究竟有多大。

最佳适应算法:

空闲分区按从小到大进行排序,自表头开始查找到第一个满足要求的自由分区分配。这样找到的空闲分区是满足作业要求且大小最小的,这种方法能使碎片尽量小。

最坏适应算法:

将所有的空闲分区按其容量从大到小顺序形成一空闲分区链,查找时只要看第一个分区能否满足作业要求。

实验内容:

1、用C或其他语言分别实现采用首次适应算法、最佳适应算法和最坏适应算法的动态分区分配过程和回收过程。
2、设置初始状态,每次分配和回收后显示出空闲内存分区链的情况。

源程序如下:

#include<iostream>
#include<stdlib.h>
using namespace std;
#define Free 0 //空闲状态
#define Busy 1 //已用状态
#define OK 1    //完成
#define ERROR 0 //出错
#define MAX_length 640  //定义最大主存信息640KB
typedef int Status;
int flag;//标志位  0为空闲区     1为已分配的工作区

typedef struct FreAarea//定义一个空闲区说明表结构
{
	long size;   //分区大小
	long address; //分区地址
	int state;   //状态
}ElemType;

typedef struct DuLNode// 线性表的双向链表存储结构
{
	ElemType data;
	struct DuLNode *prior; //前趋指针
	struct DuLNode *next;  //后继指针
}

DuLNode, *DuLinkList;
DuLinkList block_first; //头结点
DuLinkList block_last;  //尾结点
Status Alloc(int);//内存分配
Status free(int); //内存回收
Status First_fit(int);//首次适应算法
Status Best_fit(int); //最佳适应算法
Status Worst_fit(int); //最差适应算法
void show();//查看分配
Status Initblock();//开创空间表 

Status Initblock()//开创带头结点的内存空间链表
{
	block_first = (DuLinkList)malloc(sizeof(DuLNode));
	block_last = (DuLinkList)malloc(sizeof(DuLNode));
	block_first->prior = NULL;
	block_first->next = block_last;
	block_last->prior = block_first;
	block_last->next = NULL;
	block_last->data.address = 0;
	block_last->data.size = MAX_length;
	block_last->data.state = Free;
	return OK;
}

Status Alloc(int ch)//分配主存
{
	int request = 0;
	cout << "请输入需要分配的主存大小(单位:KB):"<<endl;
	cin >> request;
	if (request<0 || request == 0)
	{
		cout << "分配大小不合适,请重试!" << endl;
		return ERROR;
	}
	if (ch == 2) //选择最佳适应算法
	{
		if (Best_fit(request) == OK) cout << "分配成功!" << endl;
		else cout << "内存不足,分配失败!" << endl;
		return OK;
	}
	if (ch == 3) //选择最差适应算法
	{
		if (Worst_fit(request) == OK) cout << "分配成功!" << endl;
		else cout << "内存不足,分配失败!" << endl;
		return OK;
	}
	else //默认首次适应算法
	{
		if (First_fit(request) == OK) cout << "分配成功!" << endl;
		else cout << "内存不足,分配失败!" << endl;
		return OK;
	}
}

Status First_fit(int request)//首次适应算法
{
	//为申请作业开辟新空间且初始化
	DuLinkList temp = (DuLinkList)malloc(sizeof(DuLNode));
	temp->data.size = request;
	temp->data.state = Busy;

	DuLNode *p = block_first->next;
	while (p)
	{
		if (p->data.state == Free && p->data.size == request)
		{//有大小恰好合适的空闲块
			p->data.state = Busy;
			return OK;
			break;
		}
		if (p->data.state == Free && p->data.size>request)
		{//有空闲块能满足需求且有剩余
			temp->prior = p->prior;
			temp->next = p;
			temp->data.address = p->data.address;
			p->prior->next = temp;
			p->prior = temp;
			p->data.address = temp->data.address + temp->data.size;
			p->data.size -= request;
			return OK;
			break;
		}
		p = p->next;
	}
	return ERROR;
}

Status Best_fit(int request)//最佳适应算法
{
	int ch; //记录最小剩余空间
	DuLinkList temp = (DuLinkList)malloc(sizeof(DuLNode));
	temp->data.size = request;
	temp->data.state = Busy;
	DuLNode *p = block_first->next;
	DuLNode *q = NULL; //记录最佳插入位置

	while (p) //初始化最小空间和最佳位置
	{
		if (p->data.state == Free && (p->data.size >= request))
		{
			if (q == NULL)
			{
				q = p;
				ch = p->data.size - request;
			}
			else if (q->data.size > p->data.size)
			{
				q = p;
				ch = p->data.size - request;
			}
		}
		p = p->next;
	}

	if (q == NULL) return ERROR;//没有找到空闲块
	else if (q->data.size == request)
	{
		q->data.state = Busy;
		return OK;
	}
	else
	{
		temp->prior = q->prior;
		temp->next = q;
		temp->data.address = q->data.address;
		q->prior->next = temp;
		q->prior = temp;
		q->data.address += request;
		q->data.size = ch;
		return OK;
	}
	return OK;
}

Status Worst_fit(int request)//最坏适应算法
{
	int ch; //记录最大剩余空间
	DuLinkList temp = (DuLinkList)malloc(sizeof(DuLNode));
	temp->data.size = request;
	temp->data.state = Busy;
	DuLNode *p = block_first->next;
	DuLNode *q = NULL; //记录最佳插入位置

	while (p) //初始化最大空间和最佳位置
	{
		if (p->data.state == Free && (p->data.size >= request))
		{
			if (q == NULL)
			{
				q = p;
				ch = p->data.size - request;
			}
			else if (q->data.size < p->data.size)
			{
				q = p;
				ch = p->data.size - request;
			}
		}
		p = p->next;
	}

	if (q == NULL) return ERROR;//没有找到空闲块
	else if (q->data.size == request)
	{
		q->data.state = Busy;
		return OK;
	}
	else
	{
		temp->prior = q->prior;
		temp->next = q;
		temp->data.address = q->data.address;
		q->prior->next = temp;
		q->prior = temp;
		q->data.address += request;
		q->data.size = ch;
		return OK;
	}
	return OK;
}

Status free(int flag)//主存回收
{
	DuLNode *p = block_first;
	for (int i = 0; i <= flag; i++)
		if (p != NULL)
			p = p->next;
		else
			return ERROR;

	p->data.state = Free;
	if (p->prior != block_first && p->prior->data.state == Free)//与前面的空闲块相连
	{
		p->prior->data.size += p->data.size;//空间扩充,合并为一个
		p->prior->next = p->next;//去掉原来被合并的p
		p->next->prior = p->prior;
		p = p->prior;
	}
	if (p->next != block_last && p->next->data.state == Free)//与后面的空闲块相连
	{
		p->data.size += p->next->data.size;//空间扩充,合并为一个
		p->next->next->prior = p;
		p->next = p->next->next;
	}
	if (p->next == block_last && p->next->data.state == Free)//与最后的空闲块相连
	{
		p->data.size += p->next->data.size;
		p->next = NULL;
	}

	return OK;
}

void show()//显示主存分配情况
{
	int flag = 0;
	cout << "\n主存分配情况:\n";
	cout << "++++++++++++++++++++++++++++++++++++++++++++++\n\n";
	DuLNode *p = block_first->next;
	cout << "分区号\t起始地址\t分区大小\t状态\n\n";
	while (p)
	{
		cout << "  " << flag++ << "\t";
		cout << "  " << p->data.address << "\t\t";
		cout << " " << p->data.size << "KB\t\t";
		if (p->data.state == Free) cout << "空闲\n\n";
		else cout << "已分配\n\n";
		p = p->next;
	}
	cout << "++++++++++++++++++++++++++++++++++++++++++++++\n\n";
}

void main()//主函数
{
	int ch;//算法选择标记
	cout << "请输入所使用的内存分配算法:\n";
	cout << "(1)首次适应算法\n(2)最佳适应算法\n(3)最坏适应算法\n";

	cin >> ch;
	while (ch<1 || ch>3)
	{
		cout << "输入错误,请重新输入所使用的内存分配算法:\n";
		cin >> ch;
	}

	Initblock(); //开创空间表
	int choice;  //操作选择标记

	while (1)
	{
		show();
		cout << "请输入您的操作:";
		cout << "\n1: 分配内存\n2: 回收内存\n0: 退出\n";

		cin >> choice;
		if (choice == 1) Alloc(ch); // 分配内存
		else if (choice == 2)  // 内存回收
		{
			int flag;
			cout << "请输入您要释放的分区号:"<<endl;
			cin >> flag;
			free(flag);
		}
		else if (choice == 0) break; //退出
		else //输入操作有误
		{
			cout << "输入有误,请重试!" << endl;
			continue;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值