Synchronization of Operating System

Keywords: P4 github

Synchronization of Operating System

1. Four processes P1,P2,P3,P4 are generated by fork. Each process prints out its own name, such as P1 outputs "I am the process P1". Require P1 to execute first, P2 and P3 mutually exclusive, P4 to execute finally. Verify that the implementation is correct through multiple tests.

First, we design a precursor diagram of execution state

From the graph, it can be seen that the execution order of the four programs is: P1 first execution, P2, P3 mutually exclusive execution, P4 last execution.

So the design code is as follows:

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

int main(void){
pthread_mutex_t mutex;//Create mutexes


pid_t pid1 = fork();	//Create p1
if(pid1 == 0){
	printf("I am the process P1\n");
	return 0;
}

pthread_mutex_init(&mutex,NULL); //Initialize p2p3 mutex
waitpid(pid1, NULL, 0);	//Wait for P1 process to finish
pid_t pid2 = fork();//Create p2
if(pid2 == 0){
	pthread_mutex_lock(&mutex);	//Add exclusive lock
	printf("I am the process P2\n");
	pthread_mutex_unlock(&mutex);
	return 0;
}

waitpid(pid1, NULL, 0);//Wait until the p1 process ends
pid_t pid3 = fork();//Create p3
if(pid3 == 0){
	pthread_mutex_lock(&mutex);
	printf("I am the process P3\n");//Adding mutual latch
	pthread_mutex_unlock(&mutex);
	return 0;
}

waitpid(pid2, NULL, 0);
waitpid(pid3, NULL, 0);//Wait until p2p3 ends
pid_t pid4 = fork();//Create p4
if(pid4 == 0){
	printf("I am the process P4\n");
	return 0;
}


 
pthread_mutex_destroy(&mutex);//Destroy Mutex Locks
return 0;
}

Running the above code gives you the following results

Problem encountered: Repeated running program is still as above, can not see the mutually exclusive relationship of P2 p3, may be due to fork, leading to P3 always to enter the program before p2.

The solution is to add sleep (1) before p3 and modify the code as follows

waitpid(pid1, NULL, 0);//Wait until the p1 process ends
pid_t pid3 = fork();//Create p3
if(pid3 == 0){
	pthread_mutex_lock(&mutex);
	sleep(1);
	printf("I am the process P3\n");//Adding mutual latch
	pthread_mutex_unlock(&mutex);
	return 0;
}

Running the program again, the results are as follows:

It can be seen that the mutually exclusive relationship of p2p3 meets the experimental requirements.

2. The initial value of ticketCount for train ticket balance is 1000. There is one ticket selling thread and one ticket refund thread. Each cycle is executed many times. Add synchronization mechanism to make the result always correct. The experimental results before and after adding synchronization mechanism are required to be tested many times.

The design code is as follows:
p1p2 uses synchronization mechanism to ensure that the total number of tickets is always 1000 after the ticket is sold and refunded, thus avoiding the problem of incorrect number of tickets caused by concurrency.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#include <assert.h>

int ticket = 1000;	//The total number of tickets is 1000.
int temp = 0;
int i=100;//Execution times
sem_t blanks;	//Refund margin
sem_t datas;	//Ticket allowance

void *sold(void *arg){
	while(i--){
	sem_wait(&blanks);	
	temp = ticket;
	temp = temp - 1;
	ticket = temp;
	printf("we have %d tickets now\n",ticket);
	sem_post(&datas);	//Increase the number of tickets sold out by 1
	}
	return NULL;
}

void *back(void *arg){
	while(i--){
	sem_wait(&datas);	
	temp = ticket;
	temp = temp + 1;
	ticket = temp;
	printf("we have %d tickets now\n",ticket);
	sem_post(&blanks);	//After refunding a ticket, the balance of tickets increases by 1
	}
	return NULL;
}

int main(int argc, char *argv[]){	
	sem_init(&blanks, 0, 1000);
	sem_init(&datas, 0, 0);
	pthread_t p1, p2;//Create process
	pthread_create(&p1, NULL, sold, NULL);
	pthread_create(&p2, NULL, back, NULL);
	pthread_join(p1, NULL);
	pthread_join(p2, NULL); //Function
	sem_destroy(&blanks);
	sem_destroy(&datas);
        printf("we have %d tickets now\n",ticket);
	return 0;
}

First, set up and run 100 times to get the result.

The operation results are correct. First, 100 tickets are sold, then 100 tickets are refunded. Finally, 1000 tickets are obtained, which is correct.

Change the number of tickets to 1500, which exceeds the total number of tickets. The following results are obtained:

The results are still correct.

Change the number of runs to 15000, and the results are as follows.

The final number of votes is 0, which is incorrect.
The problem of analysis may be the problem of reading dirty data, because the number of cycles leads to the process switching and the final result is out of order.

3. A producer and a consumer thread synchronization. Set up a thread-shared buffer, char buf[10]. A thread continuously inputs characters from the keyboard to buf, and a thread continuously outputs buf content to the display. The characters and order required to be output and input are exactly the same. (In the output thread, sleep for one second at a time, and then input at different speeds to test whether the output is correct). The experimental results before and after adding synchronization mechanism are required to be tested many times.

Two semaphores are used to synchronize the two processes. At the same time, two more int variables are defined to facilitate the pointer movement of the input array.
The design code is as follows:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#include <assert.h>

char buf[10] = {0};//Initialization

sem_t blanks;	
sem_t datas;	
int readnum =0;//Number of input characters
int writenum =0;//Write out the number of characters

void *doread(void *arg){
	while(readnum<10) {
	sem_wait(&blanks);	//Free Memory Judgment
	scanf("%c",&buf[readnum]);
	readnum++;
	readnum %= 10;	//More than 10 digits are automatically eliminated
	sem_post(&datas);	//Number of input characters plus 1
	}
	return NULL;
}

void *dowrite(void *arg){
	while(writenum<10) {
	sem_wait(&datas);	//Determine whether there are input characters
	printf("%c ",buf[writenum]);
	sleep(1);			//sleep(1)
	writenum++;
	writenum %= 10;//More than 10 digits are automatically eliminated
	sem_post(&blanks);	//Free memory plus 1	
	return NULL;
}

int main(int argc, char *argv[]){	
	sem_init(&blanks, 0, 10);
	sem_init(&datas, 0, 0);

	pthread_t p1, p2;//Create thread p1p2
	pthread_create(&p1, NULL, doread, NULL);	
	pthread_create(&p2, NULL, dowrite, NULL);

	pthread_join(p1, NULL);
	pthread_join(p2, NULL);

	sem_destroy(&blanks);
	sem_destroy(&datas);

	return 0;
}

The results are as follows

The running result is correct. No matter how many characters are input, the input character can be output, and the process synchronization of input and output can be realized.
Note: In addition to the two semaphores defined, the two int variables are used to store characters. The size of the string is 10, so when the variables exceed 10, the ten bits should be removed automatically and implemented by dividing the ten redundancies.

4. Process communication. Read and run the code of three mechanisms: shared memory, pipeline and message queue.

a) Verify whether receiver can correctly read the string sent by sender in the shared memory code through experimental tests. If we delete the mutually exclusive code, what is the difference between the experimental results? If the shared memory address is printed out during the sending and receiving process, are they the same? Why?
b) Has the synchronization mechanism been implemented for the communication system calls between the named pipeline and the anonymous pipeline? Experiments show how the sender and receiver synchronize. For example, under what circumstances will the sender block and the receiver block?
c) Has the synchronization mechanism been implemented for message communication system calls? Experiments show how the sender and receiver synchronize. For example, under what circumstances will the sender block and the receiver block?

(a) According to the references, two codes 4-1.c realize sender function and 4-2.c realize receiver function.

4-1.c

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/sem.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <sys/types.h> 
#include <string.h>    
int main(int argc, char *argv[])
{
    key_t  key;
    int shm_id;
    int sem_id;
    int value = 0;
   
    //1.Product the key     key = ftok(".", 0xFF);
   
    //2. Creat semaphore for visit the shared memory     sem_id = semget(key, 1, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }
   
    //3. init the semaphore, sem=0     if(-1 == (semctl(sem_id, 0, SETVAL, value)))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }
   
    //4. Creat the shared memory(1K bytes)     shm_id = shmget(key, 1024, IPC_CREAT|0644);
    if(-1 == shm_id)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
   
    //5. attach the shm_id to this process     char *shm_ptr;
    shm_ptr = shmat(shm_id, NULL, 0);
    if(NULL == shm_ptr)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }
   
    //6. Operation procedure     struct sembuf sem_b;
    sem_b.sem_num = 0;      //first sem(index=0)     sem_b.sem_flg = SEM_UNDO;
    sem_b.sem_op = 1;           //Increase 1,make sem=1     while(1)
    {
        if(0 == (value = semctl(sem_id, 0, GETVAL)))
        {
            printf("\nNow, snd message process running:\n");
            printf("\tInput the snd message: ");
            scanf("%s", shm_ptr);
   
            if(-1 == semop(sem_id, &sem_b, 1))
            {
                perror("semop");
                exit(EXIT_FAILURE);
            }
        }
   
        //if enter "end", then end the process         if(0 == (strcmp(shm_ptr ,"end")))
        {
            printf("\nExit sender process now!\n");
            break;
        }
    }
    shmdt(shm_ptr);
    return 0;
}

4-2.c

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/sem.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <sys/types.h> 
#include <string.h>    
int main(int argc, char *argv[])
{
    key_t  key;
    int shm_id;
    int sem_id;
    int value = 0;
   
    //1.Product the key     key = ftok(".", 0xFF);
   
    //2. Creat semaphore for visit the shared memory     sem_id = semget(key, 1, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }
   
    //3. init the semaphore, sem=0     if(-1 == (semctl(sem_id, 0, SETVAL, value)))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }
   
    //4. Creat the shared memory(1K bytes)     shm_id = shmget(key, 1024, IPC_CREAT|0644);
    if(-1 == shm_id)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
   
    //5. attach the shm_id to this process     char *shm_ptr;
    shm_ptr = shmat(shm_id, NULL, 0);
    if(NULL == shm_ptr)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }
   
    //6. Operation procedure     struct sembuf sem_b;
    sem_b.sem_num = 0;      //first sem(index=0)     sem_b.sem_flg = SEM_UNDO;
    sem_b.sem_op = -1;           //Increase 1,make sem=1        
    while(1)
    {
        if(1 == (value = semctl(sem_id, 0, GETVAL)))
        {
            printf("\nNow, receive message process running:\n");
            printf("\tThe message is : %s\n", shm_ptr);
   
            if(-1 == semop(sem_id, &sem_b, 1))
            {
                perror("semop");
                exit(EXIT_FAILURE);
            }
        }
   
        //if enter "end", then end the process         if(0 == (strcmp(shm_ptr ,"end")))
        {
            printf("\nExit the receiver process now!\n");
            break;
        }
    }
    shmdt(shm_ptr);
       
    //7. delete the shared memory     if(-1 == shmctl(shm_id, IPC_RMID, NULL))
    {
        perror("shmctl");
        exit(EXIT_FAILURE);
    }
   
    //8. delete the semaphore     if(-1 == semctl(sem_id, 0, IPC_RMID))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }
    return 0;
}

The following results are obtained


As you can see above, receiver successfully outputs the sender input

Run again

So there is no input DC but output dc, which is the last time the data in memory exists.

Output memory address

It can be seen that the two memory addresses are the same.

b)

Anonymous Pipeline Code

#include <stdio.h> #include <unistd.h>   
int main()
{
    int filedes[2];
    char buf[80];
    pid_t pid_1;
    if(-1 == pipe(filedes))
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
   
    pid_1 = fork();
    if(pid_1 > 0){
        // father thread         sleep(2);
        printf("This is the father thread.\n");
        char s[] = "Hello, this is written by father.\n";
        write(filedes[1],s,sizeof(s));
        close(filedes[0]);
        close(filedes[1]);
    } else if(pid_1 == 0)
    {
        read(filedes[0],buf,sizeof(buf));
        printf("%s\n",buf);
        close(filedes[0]);
        close(filedes[1]);
    }
    return 0;
}

Code run results:

Only when the parent process inputs data to the pipeline can the child process output the pipeline content.

Modify code

#include <stdio.h> #include <unistd.h>   
int main()
{
    int filedes[2];
    char buf[80];
    pid_t pid_1;
    if(-1 == pipe(filedes))
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
   
    pid_1 = fork();
   if(pid_1 > 0){
        // father thread         printf("This is the father thread.\n");
        char s1[] = "Hello, this is written by father.\n";
        char s2[] = "Babalabeba~\n";
        write(filedes[1],s1,sizeof(s1));
        write(filedes[1],s2,sizeof(s2));
        close(filedes[0]);
        close(filedes[1]);
    }else if(pid_1 == 0)
    {
        // child thread         sleep(2);
        printf("This is the child thread.\n");
        read(filedes[0],buf,sizeof(buf));
        printf("%s\n",buf);
        close(filedes[0]);
        close(filedes[1]);
    }
    return 0;
}

The results are as follows:

We delayed the parent process twice and the child process twice, and got the result as above. So we can see that the anonymous pipeline implements synchronization mechanism, but only when the parent process writes, the child process will not be blocked.

Next, let's experiment with a famous pipeline.

Named Pipeline Code 4-3.c

#include <stdio.h>
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/stat.h> 
#include <sys/ipc.h> 
#include <fcntl.h> 
#define FIFO "/home/lh/my_fifo" int main()
{
    char buf[] = "hello,world";
    int ret;
    ret = access(FIFO, F_OK);//Check whether the file exists
    if(ret != 0)
    { 
    {
            perror("mkfifo");
            exit(EXIT_FAILURE);
        }
    }
    fifo_fd = open(FIFO, O_WRONLY);//Open FLFO files in a write-only way
    if(-1 == fifo_fd)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    num = write(fifo_fd, buf, sizeof(buf));//Write to a file
    if(num < sizeof(buf))
    {
        perror("write");
        exit(EXIT_FAILURE);
    }
    printf("write the message ok!\n");
    close(fifo_fd);
    return 0;
}

4-4.c

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/stat.h> 
#include <sys/ipc.h> 
#include <fcntl.h> 
#define FIFO "/home/lh/my_fifo" int main()
{
    char buf[20] ;
    memset(buf, '\0', sizeof(buf));
    
    int ret;
    ret = access(FIFO, F_OK);//See if the file exists
    if(ret != 0)
    {
        fprintf(stderr, "FIFO %s does not existed", FIFO);
        exit(EXIT_FAILURE);
    }  
    int fifo_fd;
    fifo_fd = open(FIFO, O_RDONLY);//Open File FIFO in Read-Only Mode
    if(-1 == fifo_fd)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    int num = 0;
    num = read(fifo_fd, buf, sizeof(buf)); //read file
    printf("Read %d words: %s\n", num, buf);
    close(fifo_fd);
    return 0;
}

Because two programs are read-only and write-only, they cross-block.

operation Operation result
Run only 4-3 4-3 being blocked
Run 4-3, then 4-4 4-3, 4-4 are completed separately
Run only 4-4 4-4 obstruction
Run 4-4, then run 4-3 4-4, 4-3 are completed separately

Therefore, both anonymous and well-known pipelines have implemented synchronization mechanism. When a well-known pipeline runs without one party, the other party will be blocked. This is the same synchronization mechanism as in anonymous pipeline where the parent process has no input and the child process will be blocked.

c)
4-5.c code, client code

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/msg.h> 
#include <sys/ipc.h> 
#include <signal.h> 
#define BUF_SIZE 128    
struct msgbuf
{
    long mtype;
    char mtext[BUF_SIZE];
};
int main(int argc, char *argv[])
{
   key_t key;
    int msgId;
    printf("THe process(%s),pid=%d started~\n", argv[0], getpid());
    key = ftok(".", 0xFF);
    msgId = msgget(key, IPC_CREAT|0644);//Create a message queue
    if(-1 == msgId)
    {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
     pid_t pid;
    if(-1 == (pid = fork()))
    {
        perror("vfork");
        exit(EXIT_FAILURE);
    }
    if(0 == pid)
    {
        while(1)
        {
            alarm(0);
            alarm(100);    
            struct msgbuf rcvBuf;
            memset(&rcvBuf, '\0', sizeof(struct msgbuf));
            msgrcv(msgId, &rcvBuf, BUF_SIZE, 2, 0);//Subprocesses read messages
            printf("Server said: %s\n", rcvBuf.mtext);
        }
        exit(EXIT_SUCCESS);
    }
    else       {
        while(1)
        {
            usleep(100);
            struct msgbuf sndBuf;
            memset(&sndBuf, '\0', sizeof(sndBuf));
            char buf[BUF_SIZE] ;
            memset(buf, '\0', sizeof(buf));
            printf("\nInput snd mesg: ");
            scanf("%s", buf);
            strncpy(sndBuf.mtext, buf, strlen(buf)+1);
            sndBuf.mtype = 1;
            if(-1 == msgsnd(msgId, &sndBuf, strlen(buf)+1, 0))//Parent process sends message
            {
                perror("msgsnd");
                exit(EXIT_FAILURE);
            }
           if(!strcmp("end~", buf))
                break;
        }
        printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());
    }
    return 0;
}

4-6.c code is server code

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/msg.h> 
#include <sys/ipc.h> 
#include <signal.h> 
#define BUF_SIZE 128    
struct msgbuf
{
    long mtype;
    char mtext[BUF_SIZE];
};
int main(int argc, char *argv[])
{
    key_t key;
    int msgId;
    key = ftok(".", 0xFF);
    msgId = msgget(key, IPC_CREAT|0644);
    if(-1 == msgId)
    {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    printf("Process (%s) is started, pid=%d\n", argv[0], getpid());
    while(1)
    {
        alarm(0);
        alarm(600);    
        struct msgbuf rcvBuf;
        memset(&rcvBuf, '\0', sizeof(struct msgbuf));
        msgrcv(msgId, &rcvBuf, BUF_SIZE, 1, 0);   //Read message          
        printf("Receive msg: %s\n", rcvBuf.mtext);
        struct msgbuf sndBuf;
        memset(&sndBuf, '\0', sizeof(sndBuf));
        strncpy((sndBuf.mtext), (rcvBuf.mtext),strlen(rcvBuf.mtext)+1);
        sndBuf.mtype = 2;
        if(-1 == msgsnd(msgId, &sndBuf, strlen(rcvBuf.mtext)+1, 0))//send message
        {
            perror("msgsnd");
            exit(EXIT_FAILURE);
        }
         if(!strcmp("end~", rcvBuf.mtext))
             break;
    }
    printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());
    return 0;
}

The results are as follows

Queues are linked lists, the maximum memory length of each message is limited, so the data that can be stored in the queue is also limited, so queue knowledge and a small amount of data transfer.

5. Read the Pintos operating system, find and read the process context switching code, explain the context content of saving and restoring and the workflow of process switching.

Process switching is an indispensable step in running multiple processes. It can reclaim the processor from the process it is occupying and give the processor to the running process for use. Process switching transfers processors to other processes for use by temporarily storing data in the registers of processors that are running. These saved data will be stored in the private stack of the process. Finally, the data stored in the stack is restored to the register of the processor and the process continues to run. In this case, the data stored in the register is called the context of the process.

The flow chart is as follows:

github code link: https://github.com/mitsdisy/OS

Thank you for your guidance!

Posted by filmixt on Mon, 22 Apr 2019 16:45:36 -0700