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!