操作系统linux虚拟机的安装与进程实验

1.虚拟机的介绍说明

虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。虚拟系统通过生成现有操作系统的全新虚拟镜像,它具有真实windows系统完全一样的功能,进入虚拟系统后,所有操作都是在这个全新的独立的虚拟系统里面进行,可以独立安装运行软件,保存数据,拥有自己的独立桌面,不会对真正的系统产生任何影响 ,而且具有能够在现有系统与虚拟镜像之间灵活切换的一类操作系统。虚拟系统和传统的虚拟机(Parallels Desktop ,Vmware,VirtualBox,Virtual pc)不同在于:虚拟系统不会降低电脑的性能,启动虚拟系统不需要像启动windows系统那样耗费时间,运行程序更加方便快捷;虚拟系统只能模拟和现有操作系统相同的环境,而虚拟机则可以模拟出其他种类的操作系统;而且虚拟机需要模拟底层的硬件指令,所以在应用程序运行速度上比虚拟系统慢得多。流行的虚拟机软件有VMware(VMWare ACE)、Virtual Box和Virtual PC,它们都能在Windows系统上虚拟出多个计算机。

2.虚拟机的安装

  1. 构建虚拟机
  2. 安装操作系统
  3. 安装VMware Tools
    (ps:此步略过,关于虚拟机的安装网上有许多教程,对于新手我这里推荐别人的详细安装链接供参考。根据自己的水平,也可自行安装。)
    (https://blog.csdn.net/qq_39478237/article/details/83084515)

3.虚拟机的进程实验

3.1 Linux操作系统的进程创建
  1. 源程序代码
#include <stdio.h> 
void main()
{	
	int p1;	
	while ((p1=fork( )) == -1);	
	if (p1 == 0)		
	printf("This is a child process.\n");	
	else		
	printf("This is a parent process.\n");
}

2.实验结果:
在这里插入图片描述

3.实验结果的分析:
(1). fork()函数的使用:
①在linux系统中创建进程的唯一办法就是使用fork()函数。与其说它是一个函数,不如说它是系统调用。因为区别于函数的一点是有多个返回值。
②调用fork()函数时,从已存在的子进程(父进程)中将创建一个新进程(子进程)。子进程是父进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的数据与堆栈空间,并继承父进程的用户代码,组代码,环境变量,已打开的文件代码,工作目录和资源限制等。因为子进程几乎是父进程的完全复制,所以父子两个进程会运行同一个程序。
③子进程和父进程是通过返回值来区分的。对于父进程,fork函数返回子进程的进程号;而对于子进程,fork函数返回0。

(2). 分析:因为创建进程后,p1等于fork函数返回子进程的进程号,不等于0,所以先执行父进程语句:This is a parent process.当执行完父进程后,p1等于0,执行子进程语句:This is a child process.即为最终的结果。

3.2 带计算的进程创建
  1. 源程序代码
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
 main()
 {	
 	pid_t child;	int i=2;
 	if((child = fork())== -1 )
 	{
 		printf("fork error.\n");
 		exit(0);
 	}	
 	if(child==0)	
 	{		
 	i=i+3;	
 	printf("i=%d\n",i);
 	}	
 	i=i+5;	
 	printf("i=%d\n",i);
 }

2.实验结果:
在这里插入图片描述

3.实验结果分析:实际操作系统该程序多次运行时理论上可能输出的结果包括以下四种情况:
①fork error
②i=5 i=10 i=7
③i=7 i=5 i=10
④i=5 i=7 i=10
我的虚拟机Linux系统中该程序多次运行时理论上可能输出的结果包括以下三种情况:
①fork error
②i=5 i=7 i=10
③i=7 i=5 i=10

(1).程序运行结果出现fork error:这种情况几乎不会发生,因为执行该语句的前提是创建进程失败,fork函数返回-1值,执行该语句。但用户使用进程创建几乎都会成功。(只有当前的进程数已经达到了系统规定的上限或者系统主存不足时,才会出现进程创建失败的情况)。

(2).程序运行结果为i=5 i=10 i=7:
①.此结果为系统出现进程调度先执行子进程和后面的语句,即得到i=5和i=10;接着才执行父进程(注意执行子进程后的i值没有影响到父进程的i值),即得到i=7。
②.因为父子进程运行时间很短,中间一般不会出现进程调度,若想得到这种结果,需要在适当地方增加系统调用函数sleep(),引起进程调度:但此种情况可能为在实际操作系统中才可能出现。上机所用的linux环境规则肯与其不同,无法得到该结果。
(3).程序运行结果为i=7 i=5 i=10:
得到这种情况是最常见的情况,因为如果中间没有出现进程调度,系统会先执行父进程再执行子进程,即得到i=7,i=5,i=10,运行结果即为以上实验结果图。
(4).程序运行结果为i=5 i=7 i=10:
①.此情况是因为在执行程序时存在进程调度,系统先执行子进程,即i=5;然后又产生进程调度执行父进程即i=7,最后执行子进程及后继语句,即i=10。
②.若想得到上述情况,则同样使用sleep()函数在适当位置插入,源程序代码如下,结果如下图:

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
 main()
 { 
 	pid_t child; 
 	int i=2; 
 	if((child = fork())== -1 )
 	{ 
 	    printf("fork error.\n");  
 	    exit(0); 
 	}
        if(child==0) 
        {  
 	    i=i+3; 
 	    printf("i=%d\n",i); 
        }
        sleep(1); 
        i=i+5; 
        printf("i=%d\n",i);
}  

在这里插入图片描述

分析结果可知:系统原本是要执行父进程的,但由于sleep函数使父进程沉睡一秒,此时是挂起状态;所以系统先执行子进程得到i=5;等待父进程唤醒后,执行父进程i=7;接着执行子进程顺序执行得到i=10
3.3 exec()类函数的使用
  1. 源程序代码:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
main()
{  
   if(fork()==0)
   {  
      printf("a"); 
      execlp("./file1",0);
      printf("b");
   }	
   printf("c\n");
} 

file1.c中的代码如下:

#include <stdio.h>
 main()
 { 
   printf("d");
 }

2.实验结果:
在这里插入图片描述

3.实验结果分析:
实际操作系统上该程序运行时可能出现的结果:
①acd
②cad
③adc
虚拟机Linux系统中该程序运行时可能出现的结果:
①cd
②dc
在这里插入图片描述

(1).exec()类函数的使用:
父进程为了启动一个新的程序的执行,在UNIX/Linux系统中需要用到exec()类函数。
exec()类函数不止一个,但大致相同,在Linux只,它们分别是:exec(),execLp(),execle(),execv(),execvp(),execve()。
exec()函数族的作用是根据参数指定的文件名找到可执行文件,并用它来取代调用进程内容,换句话说,就是在调用进程内部执行 一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件,如果不是可以执行的文件,那么就解释成为一个shell文件。
一个进程一旦调用了exec()类函数,系统将该进程的代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并根据新程序分配新的堆栈段和数据段,唯一留下的就是进程的PCB结构和进程号,也就是说,对系统而言,还是同一个进程,不过已经是一个新的程序了。

(2).分析:
与程序2结果的多可能一样,在Linux系统中运行的结果情况与实际操作系统不同(两者操作系统的制定规则可能不同)。根据上述运行结果可知,execlp()函数调用了file1文件,替代了原有的子进程,输出d。则结果为cd。当在适当位置调用sleep函数的时候,可以得到结果dc。

3.4 创建线程应用实例
  1. 源程序代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void thread(void)
{	
  int i;	
  for(i=0;i<3;i++)
  {		
    printf("this is a pthread.\n");
  }
} 
int main(void)
{	
  pthread_t id;
  int i,ret;
  ret=pthread_create(&id,NULL,(void*)thread,NULL);
  if(ret!=0)	
  {		
    printf("Create pthread error!\n");
    exit (1);
  }	
  for(i=0;i<3;i++)
    printf("This is the main process.\n");
    pthread_join(id,NULL);	
    return (0);
}

在这里插入图片描述

3.实验结果分析:

实际上该程序运行时可能出现的其它结果:
在这里插入图片描述

(1).编译连接时需要使用库libpthread.a使用命令:
gcc os_demo4.c -lpthread -o os_demo4
(2).分析:
先打印主程序语句三次,再执行线程语句三次

3.5 系统调用

目的:
(1).理解Linux系统处理系统调用的流程;
(2).掌握增加与调用系统调用的方法。

3.5.1 编写系统调用服务例程(程序描述)
  1. apt-cache search linux-source(获得虚拟机对应的内核版本)
  2. apt-get install (version number)(下载该版本的内核)
  3. 以上两步需要在linux终端窗口中下载自己虚拟机对应的linux内核
  4. 系统调用程序的实现代码在kernel/sys.c中定义代码:
asmlinkage long sys_mysyscall(const char _user *_name)
{
   char *name;
   long ret;
   name = strndup_user(_name, PAGE_SIZE); 
   if (IS_ERR(name))
   { 
      ret = PTR_ERR(name);
      goto error;
   }
   pringf("Hello, %s! This is my new syscall.\n", name); 
   return 0;
   error:
   return ret;
}  

在这里插入图片描述

3.5.2 添加系统调用号
  1. 每个系统调用都有一个唯一的系统调用号,系统根据用户传递的系统调用号,在系统调用表中寻找到相应的偏移地址的内核处理函数,进行相应的处理。
  2. 在arch/x86/include/asm/unistd.h中可以看到系统调用号的定义。
  3. (注意:我自己的虚拟机上,系统调用号在/home/linux-source-4.15.0/include/uapi/asm-generic/unistd.h中)
  4. 系统调用号的定义方式为:#define _NR_sysnameNNN。每个系统调用号前都是相应的函数名加上”NR”,而“NNN”为系统调用号,从0开始依次编号。
  5. 在unistd.h文件中添加一个新的系统调用号,只能在原来系统调用号最大值的基础上加1,假如现有系统的最大系统调用号为288,则在系统调用清单的最后新增一行:#define _NR_mysyscall 289,并修改系统调用总数:#define NR_syscalls 290。在unistd.h文件中添加一个新的系统调用号,只能在原来系统调用号最大值的基础上加1,假如现有系统的最大系统调用号为288,则在系统调用清单的最后新增一行:#define _NR_mysyscall 289,并修改系统调用总数:#define NR_syscalls 290。
  6. 下述两图为添加前和添加后。
    在这里插入图片描述

在这里插入图片描述

3.5.3 修改系统调用表

在这里插入图片描述

在这里插入图片描述

前后两图为系统调用表前后。

3.5.3 编译内核
  1. 做完以上也就对linux的操作系统的工作有了大概的认识。
  2. 关于编译内核的等待补充。
    (ps:此步比较难,我暂时卡住,待补充)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值