实验一 进程管理

Posted 青陨焱骨火

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实验一 进程管理相关的知识,希望对你有一定的参考价值。

实验一 进程创建

一、实验目的

  1. 加深对进程概念的理解,进一步认识并发执行的实质
  2. 掌握Linux操作系统的进程创建和终止操作
  3. 掌握在Linux系统中创建子进程后并加载新映像的操作。

二、实验内容
(1)编写一个C程序,使用系统调用fork( )创建一个子进程。要求:①在子进程中分别输出当前进程为子进程的提示、当前进程的PID和父进程的PID、根据用户输入确定当前进程的返回值、退出提示等信息。②在父进程中分别输出:当前进程为父进程的提示、当前进程的PID和子进程的PID、等待子进程退出后获得的返回值、退出提示等信息。
(2)编写C程序,使用系统调用fork()创建一个子进程,子进程调用exec族函数执行系统命令ls。

三、实验步骤

1.第一个实验编写过程,使用vim编辑代码然后运行

#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<sys/wait.h>
#include<stdio.h>

int main()

        pid_t childpid; /*variable to store the child's pid*/
        int retval;    /*user-provided return code for child process*/
        int status;    /*child's exit status for parent process*/

        /*create new process*/
        childpid = fork()
        if (childpid >= 0) 
                if (childpid == 0) 
                        printf("Child:I am the child process\\n");
                        printf("Child:My PID is %d\\n",getpid());
                        printf("Child:My parent's PID is %d\\n",getppid());
                        printf("Child:The value of fork return is %d\\n",childpid);
                        printf("Child:Sleep for one second...\\n");
                        sleep(1);
                        printf("Child:Enter an exit value (0~255): ");
                        scanf("%d",&retval);
                        printf("Child:Goodbye! \\n");
                        exit(retval);             /*child exits with user-provided return code*/
               
                else 
                        printf("Parent:I am the parent process!\\n");
                        printf("Parent:My PID is %d\\n",getpid());
                        printf("Parent:The value of my child's PID is %d\\n", childpid);
                        printf("Parent:I will now wait for my child to exit.\\n");
                        wait(&status);
                        printf("Parent:Child's exit code is %d\\n",WEXITSTATUS(status));
                        printf("Parent:Goodbye!\\n");
                        exit(0);

                

        
        else 
                perror("fork error\\n");
                exit(0);
        

        return 0;

2.实验2的代码

#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>

int main() 

        int rtn;                     /*the return  value of the son process*/
        if (fork() == 0) 
                /*The son process execution this process */
                execlp("/bin/ls","ls -al",(char *)0);

                exit(21);   /*If the exec function return it's program,
                             that indicate it didn't exec the order normally,
                             so it must print the failure information*/
        
        else 
                /*the father process, is waiting the end of the son process and print the return value of the son process*/
                wait(&rtn);
                printf("child process return %d\\n", rtn);
        

        return 0;

四、实验结果

实验1运行结果:

实验2 运行结果

五、实验总结与思考
(1)总结调用fork()函数的三种返回情况
当fork函数返回值为 小于0,表示没有成功创建子进程。原来的进程仍在执行
当fork函数返回值为 0,表示子进程创建成功,且当前进程为 子进程
当fork函数返回值为 大于0,表示返回值为父进程的返回
(2)总结fork()和wait()配合使用的情况,并尝试在父进程中取消wait()函数,观察进程运行情况
wait()函数的功能:
父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

Wait()函数需要和fork()函数配套使用,如果wait()函数在fork()函数之前使用,返回-1;正常在父进程中调用返回子进程的 PID

在父进程中取消wait()函数,由于没有wait函数,父进程会一直执行到进程结束,然后子进程执行的时候就会变成僵尸进程,运行结果为

(3)总结、验证exec族函数的具体使用。

六个函数的用法和相应参数
int execl(const char *path, const char *arg, …)
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, …, char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, …)
int execvp(const char *file, char *const argv[])

函数返回值 成功:函数不会返回
出错:返回-1,失败原因记录在error中

以execlp函数为例,execlp("/bin/ls",“ls -al”,(char *)0); 使用 “ls -al”将当前目录下的文件都展示出来。

操作系统实验报告 进程管理与进程通信

在这里插入图片描述

一、 实验目的

1、掌握进程的概念,明确进程的含义。
2、认识并了解进程并发执行的实质,进程的阻塞与唤醒,终止与退出的过程。
3、熟悉进程的睡眠、同步、撤消等进程控制方法。
4、分析进程竞争资源的现象,学习解决进程互斥的方法 。
5、了解什么是信号,利用信号量机制熟悉进程间软中断通信的基本原理,
6、熟悉消息传送的机理 ,共享存储机制 。

二、 实验环境

Ubuntu 20.10,gcc编译器

三、 实验内容

  1. 编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程并发执行,观察实验结果并分析原因。

  2. 用fork( )创建一个进程,再调用exec( ),用新的程序替换该子进程的内容,利用wait( )来控制进程执行顺序,掌握进程的睡眠、同步、撤消等进程控制方法,并根据实验结果分析原因。

  3. 编写一段多进程并发运行的程序,用lockf( )来给每一个进程加锁,以实现进程之间的互斥,观察并分析出现的现象及原因。

  4. 编写程序:用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
    Child process1 is killed by parent!
    Child process2 is killed by parent!
    父进程等待两个子进程终止后,输出如下的信息后终止:
    Parent process is killed!
    分析利用信号量机制中的软中断通信实现进程同步的机理。

  5. 使用系统调用msgget( ),msgsnd( ),msgrev( ),及msgctl( )编制一长度为1k的消息发送和接收的程序,并分析消息的创建、发送和接收机制及控制原理。

  6. 编制一长度为1k的共享存储区发送和接收的程序,并设计对该共享存储区进行互斥访问及进程同步的措施,必须保证实现正确的通信。

四、 实验原理 实验中用到的系统调用函数(包括实验原理中介绍的和自己采用的),实验步骤

实验原理:

实验步骤:

第一题:

因为题目说创建2个子进程,而根据fork()函数的返回值可以发现,如果返回值==0表示当前是子进程,返回值>0说明当前是父进程,根据这一点就可以通过if-else语句将程序分开成两个不同的进程运行!
在这里插入图片描述

第二题:

一开始采用跟第一题一样的创建进程的方法,只是在子进程中调用 execl()从而替代了后面的内容:
在这里插入图片描述
同时也在父进程中调用wait(),保证父进程不会提前结束:
在这里插入图片描述

第三题:

先创建2个子进程,然后在2个子进程里面都先加锁,通过for循环输出10个数字,执行结束之后再解锁:
在这里插入图片描述

第四题:

先创建2个子进程,然后在进程里面接收信号,后面需要用pause()函数来使程序暂停,直到接收到信号。接收到终止信号之后再向子进程发出信号。
在这里插入图片描述
子进程接收到信号后,终止程序的运行。
在这里插入图片描述
父进程最后调用wait()保证了父进程在最后结束。
在这里插入图片描述

第五题:

首先在main函数中创建2个进程:
父进程中调用server(),接收消息。
子进程中调用client(),发送消息。
在这里插入图片描述
client()函数中发送消息:
在这里插入图片描述
server()函数中发送消息:
先调用wait()函数等client发送完消息并结束进程再接收消息。
在这里插入图片描述

第六题:

main()函数中创建1个子进程执行server,父进程执行client
在这里插入图片描述
client中发送消息
在这里插入图片描述
server中接收消息:
在这里插入图片描述
server一接收到消息都会把addr置为-1,以等待client再次发送消息。

五、 实验结果分析(截屏的实验结果,与实验结果对应的实验分析)

1、实验结果与实验程序、实验步骤、实验原理、操作系统原理的对应分析;
2、不同条件下的实验结果反应的问题及原因;
3、实验结果的算法时间、效率、鲁棒性等性能分析。

第一题:

在这里插入图片描述

在CPU多核的情况下,通过实验结果可以发现,多次执行程序,“父进程”“子进程1”“子进程2”输出的顺序都不一样。说明3个进程的执行顺序都是不确定的,这取决于进程的调度时机。
然后把CPU调到1核,重启,
在这里插入图片描述
在这里插入图片描述
可以发现在单核的情况下,执行顺序相对固定,都是先执行的父进程再执行2个子进程。因为单核CPU在处理多线程程序时只能执行一跳指令,每个进程都是轮流执行,所以单核CPU在一定程度上控制了实验结果的随机性。

第二题:

在这里插入图片描述
执行程序,可以发现程序把当前文件夹下的所有文件都输出了。

第三题:

在这里插入图片描述
可以发现,每个进程加锁之后,都不会切换到另外2个进程去执行,只有该进程执行完毕之后解锁之后,才会切换到下一个进程去执行。

第四题:

正在运行程序:
在这里插入图片描述
开启一个新的终端,执行kill -2 54969,向父进程发送中断信号:
在这里插入图片描述
最终的运行结果如下:
在这里插入图片描述

第五题:
server中已成功接收到cilent发送的信息并显示出来。
在这里插入图片描述

第六题:
client每发送一条消息,server都会接收之后,client才会发送下一条消息。
在这里插入图片描述

六、实验总结

每一道题都是自己先在网上找了一些资料之后才开始的,花费了比较长的时间,也遇到了许多困难,比如在linux系统中操作,遇到了许多不适应的地方,很多不懂的地方也是自己花了很多时间搜索资料解决的。但是收获也很大,通过本次的实验,我也对进程、消息和共享区有了更进一步的了解。

七、实验数据及源代码(学生必须提交自己设计的程序源代码,并有注释,源代码电子版也一并提交),包括思考题的程序。

第一题

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

int main()
{
	int p;
	p = fork();
	if (p < 0)
	{
		//fork()函数的返回值<0表示进程创建失败!
		printf("子进程创建失败!\\n");
	}
	else if (p == 0)
	{
		//p==0表示这是子进程
		printf("我是第一个子进程,号码是%d\\n", getpid());
		exit(0); //中断进程
	}
	else
	{
		//p>0表示当前是父进程
		//创建第二个子进程
		int q;
		q = fork();
		if (q < 0)
		{
			printf("子进程创建失败!\\n");
		}
		else if (q == 0)
		{
			printf("我是第二个子进程,号码是%d\\n", getpid());
			exit(0);
		}
		else
		{
			printf("我是父进程,号码是%d\\n", getpid());
			exit(0); //中断进程
		}
	}
}

第二题

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main()
{
	int p;
	p = fork(); //创建子进程
	switch (p)
	{
	case -1:
		printf("创建失败\\n");
		exit(1);
	case 0: //子进程
		execl("/bin/ls", "ls", NULL);
		printf("execfail!\\n");
		exit(1);
	default:		//父进程
		wait(NULL); //先暂停父进程,避免父进程提前结束!
		printf("lscompleted!\\n");
		exit(0);
	}
}

第三题

#include <stdio.h>
#include <unistd.h>
int main()
{
    int p;
    int i;
    p = fork(); //创建子进程
    if(p < 0){
        printf("第一个子进程创建失败\\n");
    }else if(p == 0){
        //当前是子进程
        lockf(1,1,0);   //加锁
        for(i = 0;i <10;++i){
            printf("%d\\n",i);
        }
        printf("第一个子进程执行完成\\n");
        lockf(1,0,0);   //解锁
    }else{
        p = fork();  //创建第二个子进程
        if(p < 0){
            printf("第二个子进程创建失败\\n");
        }else if(p == 0){
            //子进程
            lockf(1,1,0);   //加锁
            for(i = 10;i <20;++i){
                printf("%d\\n",i);
            }
            printf("第二个子进程执行完成\\n");
            lockf(1,0,0);   //解锁
        }else{
            //当前是父进程
            lockf(1,1,0);   //加锁
            for(i = 20;i <30;++i){
                printf("%d\\n",i);
            }
            printf("父进程执行完成\\n");
            lockf(1,0,0);   //解锁
        }
    }
}

第四题

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include<sys/wait.h>

void sighandler(int sig){
    //printf("Response\\n");
}

int main(){

    int pid1;
    int pid2;
    pid1 = fork(); //创建第一个子进程
    if(pid1 == 0){
        //第一个子进程
        printf("我是第一个子进程,pid=%d\\n",getpid());
        signal(SIGUSR1,sighandler);   //接受信号
        pause();   //程序暂停,直到信号出现
        printf("Child process1 is killed by parent!\\n");  
        exit(1);
    }else{
        pid2 = fork(); //创建第二个子进程
        if(pid2 == 0){
            // 第二个子进程
            printf("我是第二个子进程,pid=%d\\n",getpid());
            signal(SIGUSR2,sighandler);   //接受信号
            pause();   //程序暂停,直到信号出现
            printf("Child process2 is killed by parent!\\n");
            exit(2);
        }else{
            //父进程
            printf("我是父进程,pid=%d\\n",getpid());
            signal(SIGINT,sighandler);
            pause();
            kill(pid1,SIGUSR1); //向进程pid1发送用户自定义信号1
            kill(pid2,SIGUSR2); //向进程pid2发送用户自定义信号2
            wait(NULL);
            wait(NULL);
            wait(NULL);         //等待两个子进程
            printf("Parent process is killed!\\n");
            exit(0);
        }
    }
}

第五题

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<string.h>
#include<sys/wait.h>

#define MSGKEY 1024  //消息key

typedef struct msgForm{ //消息结构体
    long  mtype;            /*消息类型*/
    char  mtext[1024];      /*消息的文本*/
}msgForm;

int msgqid;

void client(){
    msgForm msg;
    msg.mtype = 10; //定义消息类型
    strcpy(msg.mtext,"Hello,World!!!");
    msgqid = msgget(MSGKEY,0777); //创建消息队列
    msgsnd(msgqid,&msg,sizeof(msg),0);
    printf("(client)已发送信息!\\n");
    printf("(client)发送的信息为%s\\n",msg.mtext);
    exit(0);
}

void server(){
    msgForm msg;
    msgqid=msgget(MSGKEY,0777|IPC_CREAT); //创建一个所有用户都可以读、写、执行的队列
    wait(0);    //保证cilent发送完消息并结束进程之后才继续执行下面
    msgrcv(msgqid,&msg,sizeof(msg),0,0);    //接受信息
    printf("(server)已接受信息!\\n");
    printf("(server)接受到的信息为%s\\n",msg.mtext);
    msgctl(msgqid, IPC_RMID,0); //消除消息队列的标识符。
    exit(0);
}


int main(){
    int p;
    p = fork(); //创建子进程
    if(p == 0){
        client();
    }else{
        server();
    }
    return 0;
}

第六题

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define SHMKEY 1024 // 共享存储区的key
int shmid, i;
int *addr;

void client()
{
    int i;
    shmid = shmget(SHMKEY, 1024, 0777 | IPC_CREAT); /*创建一个共享存储区,名字为75,大小为1024字节,不重复创建*/
    addr = shmat(shmid, 0, 0);                      /*共享存储区所附接到的进程虚地址(首地址)*/
    for (i = 9; i >= 0; i--)
    {
        while (*addr != -1); //确保服务端收到了一条信息,再发下一条
        printf("(client)sent\\n");
        *addr = i;
    }
    exit(0);
}

void server()
{
    shmid = shmget(SHMKEY, 1024, 0777 | IPC_CREAT); //创建一个共享存储区,大小为1024字节
    addr = shmat(shmid, 0, 0);                      //共享存储区所附接到的进程虚地址(首地址)
    do
    {
        *addr = -1;
        while (*addr == -1);//响应客户端
        printf("(server)received\\n");
    } while (*addr);
    shmctl(shmid, IPC_RMID, 0); //撤消共享存储区,归还资源
    exit(0);
}

void main()
{
    int p;
    p = fork(); //创建子进程
    if(p < 0){
        printf("子进程创建失败!\\n");
    }
    else if(p == 0){
        server();
    }else{
        client();
    }

}

八、思考题

1、进程创建与进程并发执行

(1)系统是怎样创建进程的?
答:通过fork()系统调用之后,执行以下操作:
① 申请空白PCB(过程控制块)。
② 为新工序分配资源。
③ 初始化PCB。
④ 将新进程插入就绪队列。

(2)当首次调用新创建进程时,其入口在哪里?
答:进程的进程控制块(PCB)结构中有指向其TTS(任务状态段)的指针,TTS里面存放着进程的入口。

(3)利用strace 和ltrace -f -i -S ./executable-file-name查看程序执行过程,并分析原因,画出进程家族树。
在这里插入图片描述 在这里插入图片描述
执行过程如图所示,父进程先创建的子进程2再创建子进程1,并且在输出内容后被杀死。进程家族树如图所示:
在这里插入图片描述

2、进程的睡眠、同步、撤消等进程控制

(1)可执行文件加载时进行了哪些处理?
答:进程用exec( )装入命令ls ,exec( )后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。

(2)什么是进程同步?wait( )是如何实现进程同步的?
答:进程同步是指:在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。wait()函数一调用,父进程就会先暂停执行,直到有一个进程结束之后才去控制。

(3)wait( )和exit()是如何控制实验结果的随机性的?
答:子进程在使用了execl()函数之后,列出了当前目录下所有的文件,执行完这个函数之后才去调用exit(),退出当前进程,才去接着继续执行父进程,所以我们可以发现父进程的lscompleted!永远在最后才输出,这样子就控制了实验结果的随机性。

3、多进程通过加锁互斥并发运行

(1)进程加锁和未上锁的输出结果相同吗? 为什么?
答:不相同。因为进程加锁之后,就保证了在执行其中一个进程的时候,不会切换到另外2个进程去执行,只有解锁之后才会切换到另外2个进程。如果进程未上锁,那么程序就会随时切换到另外2个进程执行,输出结果会不相同。

4、进程间通过信号机制实现软中断通信

(1)为了得到实验内容要求的结果,需要用到哪些系统调用函数来实现及进程间的通信控制和同步?
答:kill()和signal()

(2)kill( )和signal( )函数在信号通信中的作用是什么?如果分别注释掉它们,结果会如何?
答:

  • kill()函数的作用是向指定的子进程发送信号,signal()的作用是接收指定的信号,signal()后面需要配合pause()函数一起使用。
  • 如果注释掉kill(),那么子进程将接收不到信号,导致子进程一直在等待信号的出现,父进程也一直在等待子进程结束,最终程序一直阻塞。
  • 如果注释掉signal(),那么父进程也接收不到中断信号,发出中断信号后程序也没反应,程序也是一直阻塞。

5、消息的发送与接收

(1)为了便于操作和观察结果,需要编制几个程序分别用于消息的发送与接收?
答:两个程序,其中client发送消息,server接收消息。

(2)这些程序如何进行编辑、编译和执行?为什么?
答:
① 两个程序分别编辑,执行gcc client.c -o client和gcc server.c -o server
② 执行: ./server和 ./client
③ client和server是两种不同的程序,需要分开来编辑、编译和执行,client发送请求,server用于接收消息。

(3)如何实现消息的发送与接收的同步?
答:发送程序和接收程序都必须一直做好相互通信的准备。

6、进程的共享存储区通信

(1)为了便于操作和观察结果,需要如何合理设计程序来实现子进程间的共享存储区通信?
答:每个进程都需要开辟一个共享存储区,然后附加到自己的一个内存空间中,就可以正常地进行读写操作了。

(2)比较消息通信和共享存储区通信这两种进程通信机制的性能和优缺点。
答:
①消息队列的建立比共享区的设立消耗的资源少。前者只是一个软件上设定的问题,后者需要对硬件的操作,实现内存的映像,当然控制起来比前者复杂。如果每次都重新进行队列或共享的建立,共享区的设立没有什么优势。
②当消息队列和共享区建立好后,共享区的数据传输,受到了系统硬件的支持,不耗费多余的资源;而消息传递,由软件进行控制和实现,需要消耗一定的cpu的资源。从这个意义上讲,共享区更适合频繁和大量的数据传输。
③消息的传递,自身就带有同步的控制。当等到消息的时候,进程进入睡眠状态,不再消耗cpu资源。而共享队列如果不借助其他机制进行同步,接收数据的一方必须进行不断的查询,白白浪费了大量

以上是关于实验一 进程管理的主要内容,如果未能解决你的问题,请参考以下文章

实验五 Linux系统管理

实验六 进程基础

基于计算机操作系统的Linux的进程管理

请教一个Linux下C语言的进程间的信号问题

Linux课程实验总结

实验六 进程基础