关于操作系统实验

任务1:华为云上openEuler操作系统环境

一、进程相关编程实验
a) 观察进程调度
b) 观察进程调度中的全局变量改变
c) 在子进程中调用system函数
d) 在子进程中调用exec族函数

在此实验中完成:
(1)熟悉操作命令、编辑、编译、运行程序。完成操作系统原理课程教材P103作业 3.7 (采用图3-32所示的程序)的运行验证,多运行程序几次观察结果;去除wait后再观察结果并进行理论分析。
(2)扩展图3-32的程序,添加一个全局变量并在父进程和子进程中对这个变量做不同操作,输出操作结果并解释;在return前增加对全局变量的操作并输出结果,观察并解释;修改程序体会在子进程中调用system函数和在子进程中调用exec族函数;

二、线程相关编程实验
创建两个线程运行后体会线程共享进程信息的知识。
扩展要求:
定义全局变量,在父子进程分别操作,观察结果并解释现象
父子进程运行不同程序(可使用exec函数实现)

使用pthread实现多线程(可直接得满分)

==================================

一、进程相关编程实验

实验内容(1)

熟悉操作命令、编辑、编译、运行程序。完成操作系统原理课程教材P103作业 3.7 (采用图3-32所示的程序)的运行验证,多运行程序几次观察结果;去除wait后再观察结果并进行理论分析。

实验流程
下载putty和winscp
在putty上成功登陆
log on putty
遇到的问题1:第一次编译时程序报错,因为无法识别函数wait(NULL)

编译错误
分析原因,没有包含的头文件#include<sys/wait.h>
修改后成功编译
书上的代码

#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>

int main()
{
        pid_t pid,pid1;
        /*fork child process*/
        pid =fork();

        if(pid<0){/* error occured */
                fprintf(stderr,"Fork Failed");
                return 1;
        }
        else if(pid == 0){/*child process */
                pid1 = getpid();
                printf("child: pid = %d\n", pid);/* A */
                printf("child: pid1 = %d\n", pid1);/* B */

        }
        else{/* parent process */
                pid1 = getpid();
                printf("parent: pid = %d\n", pid);/* C */
                printf("parent: pid1 = %d\n", pid1);/* D */
                wait(NULL);
        }
        return 0;

}

包含wait(NULL)运行结果:
new_with_wait

解释:

  • 分析各个pid值相同与不同的原因: fork()
      函数调用一次,返回两次。两次返回的区别是:子进程的返回值是0,父进程返回值为新子进程的进程ID。
      调用fork()函数以后,若未成功创建子进程,则函数返回值为-1;若成功创建子进程,其中父进程的返回值为子进程的地址,子进程的返回值为0,余下代码由父子进程共同执行。
      对于子进程,child:pid值为0,子进程进入else if(pid==0)语句以后,调用getpid()函数返回值为调动函数的进程的地址,即其自身的地址(child:pid1为子进程的地址)。
      对于父进程,parent:pid值为子进程的地址,进入else语句后,调用getpid()函数,其返回值为调用函数的进程的地址,即其自身的地址(parent:pid1为父进程的地址)。
      综上所述, Parent:pid的值应与child:pid1的值相同,都为子进程的地址; Parent:pid1的值为父进程的地址;Child:pid的值为0。

不包含wait(NULL)运行结果:

new_without_wait

  • 分析有无wait()函数造成结果差异的原因:
      如果在父进程部分加入语句wait(NULL),意味着父进程会等待子进程退出后发出信号后再退出,导致父进程会等待子进程结束。
      如果在父进程部分没有加入语句wait(NULL),意味着父进程会等待子进程退出后发出信号后再退出,导致父进程会等待子进程结束。(如图结果所示,在包含wait(NULL)函数时,父进程先退出,在未包含时,子进程先退出)
      fork出的父子进程并不保证任何有规律的执行顺序! 不同linux发行版,不同的系统配置环境,不同的系统负载下很可能结果就不一样了。也就是说,在退出之前执行的语句,实际上每一次都是随机的,由操作系统的调度算法决定。

实验内容(2)

扩展图3-32的程序,添加一个全局变量并在父进程和子进程中对这个变量做不同操作,输出操作结果并解释;在return前增加对全局变量的操作并输出结果,观察并解释;修改程序体会在子进程中调用system函数和在子进程中调用exec族函数;

添加全局变量并在父进程和子进程中对这个变量做不同操作

实验步骤

#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<unistd.h>
int global=3;//定义全局变量
int main()
{
        pid_t pid,pid1;
        /*fork child process*/
        int local=3;//定义局部变量
        pid =fork();

        if(pid<0){/* error occured */
                fprintf(stderr,"Fork Failed");
                return 1;
        }
        else if(pid == 0){/*child process */
                global++;
                printf("i am child: global = %d\n", global);

        }
        else{/* parent process */
                global--;
                printf("i am parent: global = %d\n", global);
                wait(NULL);
        }
        return 0;
}

运行结果:
加入全局变量
解释:
  子进程创建以后,会继承父进程的全局变量(child:global=3),(parent:global=3)但是继承的是父进程刚开始全局变量的值。
  但是子进程创建以后,子进程修改了变量,或者父进程修改了全局变量的值,父子进程就互相都不影响了。
  如图所示,父进程对其自己堆中的全局变量global进行–操作,得到global=2,子进程再对自己堆中的全局变量global进行++操作,得到global=4。

global--
global++
parent global=3
fork
child global=3
parent global=3
parent global=2
parent global=4
system函数与exec函数

首先在网上查阅资料了解exec()和system()的功能

exec族函数功能:用exec函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID。
在子进程中调用system函数

system函数执行了三步操作:

  1. fork一个子进程;
  2. 在子进程中调用exec函数去执行command;
  3. 在父进程中调用wait去等待子进程结束。对于fork失败,system()函数返回-1。
    如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值。
    如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127。
    如果command为NULL,则system()函数返回非0值,一般为1。

使用system函数

#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
        pid_t pid,pid1;
        /*fork child process*/
        pid =fork();
        if(pid<0){/* error occured */
                fprintf(stderr,"Fork Failed");
                return 1;
        }
        else if(pid == 0){/*child process */
				printf("I am child\n", pid1);
				system("ls -a");
                printf("I am child again\n", pid1);
        }
        else{/* parent process */
                printf("i am parent\n");
        }
        return 0;
}

运行结果:运行了子进程中execl()后的语句

在这里插入图片描述

使用execl函数

#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
        pid_t pid,pid1;
        /*fork child process*/
        pid =fork();
        if(pid<0){/* error occured */
                fprintf(stderr,"Fork Failed");
                return 1;
        }
        else if(pid == 0){/*child process */
				printf("I am child\n", pid1);
				execl( "/bin/ls","ls","-a",NULL);
                printf("I am child again\n", pid1);
        }
        else{/* parent process */
                printf("i am parent\n");
        }
        return 0;
}

运行结果:未运行子进程中execl()后的语句
在这里插入图片描述
返回子进程的id

解释结果:
system()和exec()都可以执行进程外的命令,system是在原进程上开辟了一个新的进程,去执行特定的任务,但是exec是用新进程(命令)覆盖了原有的进程。

线程pthread

根据操作系统教材中得到的知识,线程是轻量化的线程。其共享code&data,优先权,操作系统资源,但是不共享PC、寄存器、栈指针/空间。 一个进程可以有很多线程,每条线程并行执行不同的任务。
可以这么理解,线程中没有堆这个概念,所有线程共享一个堆区域。
在进行实验前,我们首先需要熟悉线程/进程间的同步和互斥概念。

互斥:是指三部在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。互斥操作是为了解决进程之间的协作关系而建立的一种机制。
同步:是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的 某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。  
同步操作是为了解决进程之间的协作关系而引入的一种机制。
不难看出,进程互斥关系是一种特殊的进程同步关系,即逐次使用互斥共享资源,也是对进程使用资源次序上的一种协调。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int a = 1; // 全局变量
void run() {    ++a;    printf("%d\n",a);}
int main() {    
 	int tmp1, tmp2;
	pthread_t thread1, thread2;	
	int ret_thrd1, ret_thrd2;
//线程1--------------------------
	ret_thrd1 = pthread_create(&thread1, NULL, (void *)&run,  NULL);
	if (ret_thrd1 != 0) {
	printf("thread1 create error\n");
	 } else {
	printf("thread1 create success\n");
	}
	pthread_join(thread1,NULL);
//线程2--------------------------
	ret_thrd2 = pthread_create(&thread2, NULL, (void *)&run, NULL);
	if (ret_thrd2 != 0) {
	 printf("thread2 create error\n");
	 } else {
	printf("thread2 create success\n");
	}
	pthread_join(thread2,NULL);	
}

运行结果
在这里插入图片描述
结果分析:
由于两个线程共享全局变量,因此对其做++操作时即是对全局变量做两次++操作。

如果我们重写run()函数,并让thread执行不同的run()函数,看看pthread_join对线程之间执行顺序有什么影响。

int a = 1; // 全局变量
void run() {  int i=0; while(i<100){ a=a+5;i++;printf("%d\n",a);}    }
void run2() {  int i=0;  while(i<100){ a=a-1;i++;printf("%d\n",a);}   }
int main() {    
 	int tmp1, tmp2;
	pthread_t thread1, thread2;	
	int ret_thrd1, ret_thrd2;

	ret_thrd1 = pthread_create(&thread1, NULL, (void *)&run,  NULL);
	if (ret_thrd1 != 0) {
		printf("thread1 create error\n");
	 } 
	else {
		printf("thread1 create success\n");
	}
	pthread_join(thread1,NULL);
	ret_thrd2 = pthread_create(&thread2, NULL, (void *)&run2, NULL);
	if (ret_thrd2 != 0) {
	 	printf("thread2 create error\n");
	 } else {
		printf("thread2 create success\n");
	}
	pthread_join(thread2,NULL);
}

部分结果
以上是输出部分结果,可以看出线程thread1先执行完run()后,线程2才开始执行run2();
倘若我们去除pthread_join()函数,再观察结果。
在这里插入图片描述

结果可以看出,线程之间的同步关系被打破了,线程thread1还未执行完,就被thread2强占了资源,而输出结果远远没有达到100次,这是由于操作系统内部实现了线程之间的互斥操作,thread2和thread1同一时间只有一个可以对临界区(critical section)进行操作。

由此,我们可以得出pthread_join()函数的作用:以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。

在代码中,当thread1运行结束之后对资源进行回收,在这之前不允许thread2对全局变量进行操作,这实现了进程之间的互斥。

代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了(这是thread2也没有执行100次的原因)。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,而在今天的例子中,主线程会等待线程的结束才继续执行下面的语句(创建thread2),使创建的线程有机会执行。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值