Operating system experiment - C++ program implementation of Fibonacci sequence (parent-child program fork() pure version and shared memory version)

        In the operating system class this semester, I did two computer experiments, both of which were related to the generation of Fibonacci sequence, and involved two important knowledge points - the establishment and connection of parent and child programs, and the use of shared memory. In this blog, the two methods will be explained in detail to provide reference for comrades in need.

Table of contents

Fork () concept understanding

Parent-child program fork() pure version

Idea one

Idea two

shared memory version

1. Define the structure

2. Create shared memory

  2.1 Create an integer identity value for shared memory

  2.2 Add shared memory to address space

3. Enter the number of Fibonacci sequence interactively and create parent program and subroutine

4. Reclaim shared memory

  4.1 Separate the shared memory segment from the memory space

  4.2 Completely delete the shared memory segment

flow chart

source code

The result of the operation is as follows

Summarize


Fork () concept understanding

        Before explaining the implementation of the program, I still need to explain the concept of fork() to everyone, because when communicating with peers, I found that everyone is still a little vague about this concept, and many people do not understand how fork() is fork( ), here I use a program to demonstrate.

int main(){
	int a=5, b=7;
	pid_t  pid;
	pid = fork();		/* fork another process */
	if (pid < 0) {		 /* error occurred */
		fprintf(stderr, "Fork Failed");
		exit(-1);
	}
	else if (pid == 0) { 	/* child process */
		a=a*2;
		printf (“this is Child process, a=%d, b=%d\n”,a,b);
	}
	else {			 /* parent process */
		printf (“this is parent process, a=%d, b=%d\n”,a,b);
		wait (NULL);/* parent will wait for the child to complete */
		a=a+1; b=b+3;
		printf (“Child Complete, a=%d, b=%d\n”,a,b);
		exit(0);
}   }

 First of all, when the parent process creates a child process, it must first define a variable of type pid_t. In the above program, we named the variable pid. The function of this variable is to distinguish who is the parent process and who is the child process.

The function of the fork() function is to create a child process and return a value of type pid_t at the same time, so in the above program there is

pid = fork();

This operation will create a process that is exactly the same as the parent process, which is what we call a child process. At this point, the pid value comes into play.

Because there are pids in the parent and child processes, then——

If the pid is 0, it means that the process is a child process; if the pid is not 0, but a certain positive integer, then it means that the process is the parent process, the integer is actually the identifier of the child process, and the parent process needs to obtain the child process. Only the identifier of the process can know which one is your son, right (hehe).

After determining the value of pid, we can run according to the branch statement in the program. The child process runs the code segment with pid = 0, and the parent process runs the code segment with pid > 0, step by step.

at this time! One word must be kept in mind! ——

The child process and the parent process are two separate process spaces!

The child process and the parent process are two separate process spaces!

The child process and the parent process are two separate process spaces!

In other words, the child process does the tasks of the child process, and the parent process does the tasks of the parent process, and the two do not affect each other. The operation of the child process will not affect the parent process, and the generated value will not be returned to the parent process; similarly, the operation of the parent process will not affect the child process.

For the parent process (the red section is the running code of the parent process):

int main(){

  pid_t  pid;  int a=5, b=7;

  pid = fork();  /* fork another process */

  if (pid < 0) {  /* error occurred */

  fprintf(stderr, "Fork Failed");

  exit(-1);

  }

  else if (pid == 0) {   /* child process */

  a=a*2;

  printf (“this is Child process, a=%d, b=%d\n”,a,b);

  }

  else {  /* parent process */

  printf (“this is parent process, a=%d, b=%d\n”,a,b);

  wait (NULL);/* parent will wait for the child to complete */

  a=a+1; b=b+3;

  printf (“Child Complete, a=%d, b=%d\n”,a,b);

  exit(0);

}

There is a line of code in the running code of the parent program as wait(NULL), and the function of this line of operation is to wait for the completion of the subroutine. That is to say, only after the subroutine finishes running, the parent program will perform the operation after the wait(NULL) statement.

 For child processes (the red section is the running code of the child process):

int main(){

  pid_t  pid;  int a=5, b=7;

  pid = fork();  /* fork another process */

  if (pid < 0) {  /* error occurred */

  fprintf(stderr, "Fork Failed");

  exit(-1);

  }

  else if (pid == 0) {   /* child process */

  a=a*2;

  printf (“this is Child process, a=%d, b=%d\n”,a,b);

  }

  else {  /* parent process */

  printf (“this is parent process, a=%d, b=%d\n”,a,b);

  wait (NULL);/* parent will wait for the child to complete */

  a=a+1; b=b+3;

  printf (“Child Complete, a=%d, b=%d\n”,a,b);

  exit(0);

}

Then before wait(NULL), the subroutine will print " this is Child process, a=10, b=7 ", and the parent process will print " this is parent process, a=5 , b=7 ".

Here we need to pay attention - the content printed by the child process and the parent process is not determined in time scale. In other words, the parent process may print first, and the child process may print later, or the child process may print, and the parent process may print later.

So the program running here will produce different results, and the difference lies in the order of printing!

So for this program, there are three final results:

  • Fork  Failed
  • this is Child process, a=10, b=7

        this is parent process, a=5, b=7

        Child Complete, a=6, b=10

  • this is parent process, a=5, b=7

        this is Child process, a=10, b=7

        Child Complete, a=6, b=10

In general, the focus of fork is the difference in output results. Of course, in actual operation, the nature of the system itself may determine the order of generation, so there may be only one final output result, but this is the actual system operation. In the above situation, what we have analyzed above are all possible situations, as long as everyone is clear.

Then enter the code link~

(Note that the code needs to be compiled and generated in the linux environment. When compiling in VS, VS does not contain some header files and cannot be compiled. Some header files need to be added manually. I will not expand here, and you can refer to the blog by yourself)

Parent-child program fork() pure version

        In this method, the parent-child program is generated by the fork() command, and the Fibonacci sequence is generated and printed in the sub-program, and the parent program only needs to wait for the completion of the sub-program.

        In actual implementation, there are actually many options to choose from. Here are two simple ideas——

        Idea 1: Define a function outside the main() function to create a parent-child program and add an iteration, so that the sub-program creates its own sub-program, and the sub-program of the sub-program creates its own sub-program, nested layer by layer, each sub-program The program only prints a Fibonacci number that belongs to its own jurisdiction.

        Idea 2: Define a global variable vector<int> array in advance, define only one parent program, and generate only one subroutine. The subroutine continuously iteratively generates Fibonacci numbers according to the recursive formula and stores them in the array of global variables. The parent program will The array of global variables can be printed out.

        It seems that the second way of thinking is easier to think of. After all, there is only a pair of parent-child programs, and the structure is not so complicated; for method 1, the layer-by-layer nesting of parent-child programs may be a bit convoluted, but it is quite interesting after thinking about it. (I thought of the first one at first, but later I discovered the second way of thinking, u1s1 and the second one are really simpler).

Idea one

-------------------------------------------------- ---Quick description--------------------------------------------- -------------------

Define a function outside the main() function to create a parent-child program and add an iteration, so that the sub-program creates its own sub-program, and the sub-program of the sub-program creates its own sub-program, nested layer by layer, and each sub-program only prints It belongs to a Fibonacci number that should be governed by itself.

---------------------------------------------------------------------------------------------------------------------------------

The tasks we need to determine are:

1. Determine what the parent program should do

2. Determine what the subroutine should do

So for the parent program, in fact, there is really no need to do anything in the program, just wait for the subroutine to finish printing.

So for the subroutine, first we need to get the Fibonacci number we want to print, and print it out, and then enter the next level of nesting.

This kind of thinking may not be easy to describe in language, so let's take a look with the help of code.

In this program, the author defines a function ForkProcess outside the main() function, the content is as follows:

void ForkProcess(int n, int fib0, int fib1) {
            //如果Fibonacci的级数小于三,就不需要再生成了,因为初始的两个Fibonacci数我们都已知.
            if(n < 3) return;
            else {
                    pid_t pid;
                    pid = fork();
                    if(pid < 0) {
                            cout << "Error! Fork Failed!" << endl;
                            exit(-1);
                    }
                    else if(pid == 0) {
                            int temp = fib1;
                            fib1 = fib1 + fib0;
                            fib0 = temp;//获取到该级子程序应该打印的Fibonacci数.
                            cout << fib1 << endl;
                            //再生成下一级子程序并打印Fibonacci数.
                            ForkProcess(--n, fib0, fib1);
                    }
                    else wait(NULL);//父程序的操作,等待子程序完成.
                }
            }

This function has already done all the operations to generate the Fibonacci sequence. The only thing we need to do is to reference this function in the main function and pass in the series n and the initial Fibonacci sequence fib0 and fib1.

You can analyze the above function to understand nesting and parent-child program generation.

The overall code implementation is as follows:

#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>

using namespace std;

void ForkProcess(int n, int fib0, int fib1){};
//这里笔者偷个懒简写了一下,为了方便阅读main函数代码
//运行的时候把上面的代码copy过来就好

int main() {
    cout << "请输入想要生成的Fibonacci数列的数字个数" << endl;
    int n;
    int fib0 = 0;
    int fib1 = 1;
    cout << "Fibonacci数列为:" << endl;
    if(n == 1) {//如果级数为1,直接打印一个数然后退出程序
        cout << fib0 << endl;
        return 0;
    }
    else if(n == 2) {//如果级数为2,直接打印两个数然后退出程序
    cout << fib0 << endl;
    cout << fib1 << endl;
    return 0;
    }
    else {//否则,运行ForkProgress程序
    cout << fib0 << endl;
    cout << fib1 << endl;
    ForkProcess(n, fin0, fib1);
    return 0;
    }
}

So far, the first plan of idea 1 has been realized, do you feel that you can still understand it? Of course, the author thinks this is a bit difficult, and it is not easy to think of + understand it. So we might as well adopt a simpler method - idea 2.

Idea two

-------------------------------------------------- ---Quick description--------------------------------------------- -------------------

Define a global variable vector<int> array in advance, define only one parent program, and generate only one subroutine. The subroutine continuously iteratively generates Fibonacci numbers according to the recursive formula and stores them in the global variable array. The parent program saves the global variable The array can be printed out.

---------------------------------------------------------------------------------------------------------------------------------

This method only involves a pair of parent-child programs, so it is easier to understand~

First, clarify what the parent process does and what the child process does

Parent process - print all values ​​in the global variable array

Subprocess - store the generated Fibonacci numbers in the global variable array

code show as below:

#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<vector>

using namespace std;

vector<int> result;

int main() {
    int fib0 = 0;
    int fib1 = 1;
    int n;
    cout << "请输入想要生成的Fibonacci数列的级数:" << endl;
    cin >> n;
    //对两种特殊情况进行处理,即n=1和n=2的情况.
    if(n == 1) {
        cout << fib0 << endl;
        return 0;
    }
    if(n == 2) {
        cout << fib0 << endl;
        cout << fib1 << endl;
        return 0;
    }
    //如果n>3,则调用父子程序生成Fibonacci数列.
    pid_t pid;
    pid = fork();
    if(pid < 0) {
        cout << "Fork Failed" << endl;
        return 0;
    }
    else if(pid == 0) {//子程序操作
        result.push_back(fib0);
        result.push_back(fib1);
        while(n >= 3) {
            //获取下一个Fibonacci数并将其放入数组            
            int temp = fib1;
            fib1 = fib0 + fib1;
            fib0 = temp;
            result.push_back(fib1);
            --n;
        }
    }
    else {//父程序操作
        wait(NULL);//等待子程序完成
        int num = result.size();
        for(int i = 0; i < num; i++) {
                cout << result[i] << "  ";//将所有Fibonacci数打印出来
        }
        cout << endl;
    }
    return 0;
}
        
        

The focus of this code is the operation of the subroutine, that is, to continuously use fib0 and fib1 as the transfer station for generating the Fibonacci sequence to generate the array, which not only reduces the space complexity in the generation process, but also is simple and convenient. You can learn this kind of thinking.

At this point, the parent-child program fork() pure version code is over!

If you don’t understand, please leave a message and exchange, and then share the implementation method of shared memory~

---------------------------------------------------------------------------------------------------------------------------------

 In fact, I have been asking for updates for a long time in the middle, and I have not updated this part of the content, otherwise this article has been sent out long ago.

Come on!

---------------------------------------------------------------------------------------------------------------------------------

shared memory version

The topic description is roughly like this:

After reading the topic description, we should be clear that we only need to create a parent-child program once!

The subroutine is responsible for writing the corresponding number of generated Fibonacci sequences into the shared memory, and the parent program is responsible for printing out the Fibonacci sequences from the shared memory, and the topic is over!

Therefore, the focus is on how to create shared memory.

So let's first take a look at the ideas. The creation of shared memory is roughly divided into the following steps:

1. Define the structure (the purpose is to clarify the storage form of the shared memory segment of the parent-child program. If the storage form is a simple int type or char type variable, then obviously there is no need to define the structure~)

2. Create shared memory

  • Create an integer identification value of shared memory (mark serial number, mark)
  • Add the shared memory to the address space (mark it before entering)

3. Reclaim shared memory

  • Detach shared memory segment from memory space
  • Delete the shared memory segment completely

 After clarifying the general framework, let's focus on the specific details of the framework implementation. Next, I will analyze the code ideas step by step according to the implementation sequence of the program code——

1. Define the structure

        To realize the structure is to define the storage form of the shared memory segment between the subroutine and the parent program. Here we use the structure form given in the textbook to conduct experiments.

typedef struct{
    long fib_sequence[MAX_SEQUENCE];
    int sequence_size;
}shared_data;

        Among them, MAX_SEQUENCE is pre-defined as 20, and the command used is

#define MAX_SEQUENCE 20

2. Create shared memory

    2.1 Create an integer identity value for shared memory

        The commands involved are

segment_id = shmget(IPC_PRIVATE, size, S_IRUSR | S_IWUSR)

        The first parameter refers to the shared memory segment keyword (identifier). If it is assigned as IPC_PRIVATE, a new shared memory segment will be generated. The second parameter refers to the size of the shared memory segment (note that the size is calculated according to the number of bytes, here you need to define a variable of type size_t to pass parameters). The last third parameter is used to identify the mode, which specifies how to use the shared memory segment, that is, for reading, for writing, or both.

        The header files required by this directive are

#include<sys/ipc.h>
#include<sys/shm.h>

        If the integer identifier value of the shared memory is successfully created, a shared memory segment integer identifier value returned by the shmget() function will be obtained from segmeng_id; if it fails, -1 will be returned. Perform error checking, error reminder, and processing based on this feature.

  2.2 Add shared memory to address space

        The commands involved are

shared_memory = (变量类型强转)shmat(segment_id, NULL, 0);

        The underlined part of the command is adjusted according to the specific situation. Here we need to fill in shared_data* in parentheses. The specific explanation is that if the shmat() function is successfully executed, it will return a pointer to the initial location in the memory of the attached shared memory area, because we defined the shared memory as shared_data in advance. type, so the pointer here is forcibly converted to shared_data* type. Similarly, when defining shared_memory, the data type is also specified as shared_data*.
        In the shmat() function, the call requires three parameters -

  • The first is the integer identification value of the shared memory segment that you want to join, that is, segment_id;
  • The second is a pointer location in the memory, which indicates the location of the shared memory to be added. If a value NULL is passed, the operating system will select the location for the user (thus generally choose NULL);
  • The third parameter represents a flag, which specifies whether the shared memory area to be added is in read-only mode or write-only mode. By passing a parameter of 0, it means that the shared memory area can be read or written.

        The command creates the required header files with

#include <sys/types.h>
#include <sys/shm.h>

        Similarly, -1 is returned if it fails. Perform error checking, error reminder, and processing based on this property.

        At this point, the shared memory is created.

3. Enter the number of Fibonacci sequence interactively and create parent program and subroutine

        First, interact and let the program runner manually input the number of numbers of the sequence to be generated.

        If it exceeds the range of 1~MAX_SEQUENCE, it will prompt to re-enter. Defines the pid variable of type pid_t.

        Let pid = fork() to create a subroutine.

        Determine the parent and child programs based on the pid value——if it is a child program, the pid value is 0; if it is a parent program, the pid value is greater than 0; if the program fails to be created, the pid value is negative.

        The subroutine needs to generate the Fibonacci sequence and write the content into the shared memory. The parent program prints the Fibonacci sequence in the shared memory after the subroutine finishes.

4. Reclaim shared memory

  4.1 Separate the shared memory segment from the memory space

        The commands involved are:

shmdt(shared_memory)

The header files required by this command are:

#include <sys/types.h>
#include <sys/shm.h>

Similarly, -1 is returned if it fails. Perform error checking, error reminder, and processing based on this property.

  4.2 Completely delete the shared memory segment

        The commands involved are:

shmctl(segment_id, IPC_RMID, NULL);

        The header files involved in this command are:

#include <sys/types.h>
#include <sys/shm.h>

        Similarly, -1 is returned if it fails. Perform error checking, error reminder, and processing based on this property.

        At this point, the shared memory is recovered.

For detailed information on several operations of shared memory, you can directly Baidu Encyclopedia, it is really comprehensive!

Give me a portal—— https://baike.baidu.com/item/shmget/6875075

flow chart

 source code

        I hope that you can write it after you understand the idea, so I will paste the picture here, even if you type it again, it is better than directly Ctrl+C, V~

        

 The result of the operation is as follows

(Note that the code cannot be run under vs, it can only be compiled and run with g++ under linux environment)

 --------------------------------------------------------------------------------------------------------------------------------

Summarize

 It's finally over!

This part is actually not difficult to understand, the key is to practice. After understanding the use of fork, the relationship between parent and child programs, and the creation of shared memory, the rest is to organize the code!

Come and communicate with us if you have any questions! In the future, I will publish some content related to the operating system from time to time. Welcome everyone to pay attention!

Guess you like

Origin blog.csdn.net/wyr1849089774/article/details/130312176