OS第三次实验-计科1601-1628011
一、
实验题目
通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
实验思路
实验要求P1最先执行P2、P3互斥执行,因此,我设计了互斥信号量mutex,mutex初值为0,在P1执行后mutex值为1,保证了P1最先执行,P2,P3都要wait这个互斥信号量,保证了他们的互斥执行。
实验还要求P4最后执行,为此设计了两个信号量flag1和flag2,分别由P2和P3signal,P4需wait这两个信号量,之后才能执行,这样就满足了实验的全部要求
实验代码(fork1.c)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
int main(){
pid_t p1,p2,p3,p4;
sem_t* flag1= NULL;
sem_t* flag2 = NULL;
sem_t* mutex = NULL;
flag1 = sem_open("P2", O_CREAT, 0666, 0);
flag2 = sem_open("P3", O_CREAT, 0666, 0);
mutex = sem_open("mutex", O_CREAT, 0666, 0);
p1 = fork();
if(p1 > 0){
printf("I am the process P1\n");
sem_post(mutex);
sleep(1);
while((p3=fork())==-1);
if(p3>0)sleep(1);
else if(p3 == 0){
sem_wait(mutex);
printf("I am the process P3\n");
sem_post(mutex);
sem_post(flag2);
}
}
else if(p1 == 0){
while((p2=fork())==-1);
if(p2>0){
sem_wait(mutex);
printf("I am the process P2\n");
sem_post(mutex);
sem_post(flag1);
sleep(1);
while((p4=fork())==-1);
if(p4>0)sleep(1);
else if(p4 == 0){
sem_wait(flag1);
sem_wait(flag2);
printf("I am the process P4\n");
}}
}
sem_close(flag1);
sem_close(flag2);
sem_close(mutex);
sem_unlink("P2");
sem_unlink("P3");
sem_unlink("mutex");
return 0;
}
实验运行结果及分析
我们可以看到,在多次运行的情况下,进程按照P1-P2-P3-P4的顺序执行,满足了题目的要求,但还有一点小问题,没有出现P1-P3-P2-P4这样的顺序,应该在P2中加入sleep(1)这样的语句使得P3也有先执行的机会。
二、
实验题目
火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。
实验思路
首先在不设置同步机制的情况下测试售票和退票线程,观察结果,如果出现问题,则引入同步机制。
实验代码(train1.c)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
int train_tickets=1000;
sem_t* mySem = NULL;
void*sell(){
int i = 2000000 ;
while(i--){
sem_wait(mySem);
train_tickets--;
// sched_yield();
sem_post(mySem);
}
}
void*refund(){
int i = 2000000;
while(i--){
sem_wait(mySem);
train_tickets++;
// sched_yield();
sem_post(mySem);
}
}
int main(){
pthread_t p1,p2;
mySem = sem_open("train_ticket", O_CREAT, 0666, 1);
pthread_create(&p1,NULL,sell,NULL);
pthread_create(&p2,NULL,refund,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
sem_close(mySem);
sem_unlink("train_ticket");
printf("The number of train tickets is %d.\n",train_tickets);
return 0;
}
实验运行结果及分析
首先尝试运行售票sell和退票线程refund各200次,最后显示余票仍为1000。
但运行售票sell和退票线程refund各20000次时,出现错误。(显示余票数为5910)
错误分析:
当循环次数较小时,一个时间片就能处理完所有操作,不会发生错误,但循环次数变大后,一旦时间片耗尽,就会发生进程的切换,从而导致错误。
解决方式
引入同步机制,加入信号量mysem,使得售票进程sell和退票进程refund互斥,即使运行20000次,200000次扔仍能得到正确结果1000.
三、
实验题目
一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。
实验思路
字符缓冲区可以存10个字符,等效为一个拥有10个资源的信号量scanf_sem,每当读入一个新的字符,缓冲区的资源就会减少一个。
对于输出线程,缓冲区中必须有字符它才能输出,因此它的信号量print_sem初始值为0,每读入一个字符,print_sem加1。
为了防止输入10个以上字符时缓冲区溢出,利用==buf[i++%10]==来写入,就不会出现溢出的问题了。
实验代码(produce.c)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t* scanf_sem = NULL;
sem_t* print_sem = NULL;
char buf[10];
int i,j = 0;
void*scanf_char(){
while(1){
sem_wait(scanf_sem);
char *t = &buf[i++%10];
scanf("%c",t);
sem_post(print_sem);
}
}
void*print_char(){
while(1){
sleep(1);
sem_wait(print_sem);
char *t = &buf[j++%10];
if(j==9)
j=0;
printf("%d:%c\n",j,*t);
sem_post(scanf_sem);
}
}
int main(){
pthread_t p1,p2;
scanf_sem = sem_open("scanf", O_CREAT, 0666, 10);
print_sem = sem_open("print", O_CREAT, 0666, 0);
pthread_create(&p1,NULL,scanf_char,NULL);
pthread_create(&p2,NULL,print_char,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
sem_close(scanf_sem);
sem_close(print_sem);
sem_unlink("scanf");
sem_unlink("print");
return 0;
}
实验运行结果及分析
快速输入字符串时,程序依然能有序的输出字符。
四
1 共享内存
实验题目
通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?
实验代码
sender.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;
}
Receiver.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;
}
实验结果及分析
可以看到,receiver能够正确读出sender发送的字符串。
然后我们删除其中互斥的代码。
删除互斥语句后的sender2.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)
{
printf("\nNow, snd message process running:\n");
printf("\tInput the snd message: ");
scanf("%s", shm_ptr);
//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;
}
删除互斥语句后的receiver2.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)
{
printf("\nNow, receive message process running:\n");
printf("\tThe message is : %s\n", shm_ptr);
//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;
}
可以看到,在删除互斥访问的语句后,sender进程因为在等待用户的输入,一直停留,而receiver进程则会不停地访问共享的内存,不停输出用户输入的字符串。
打印Sender和Receiver进程中共享内存的地址
分别在Sender和Receiver中对应出加入如下语句:
printf("sender中共享内存的地址为:%p\n",shm_ptr);
printf("receiver中共享内存的地址为:%p\n",shm_ptr);
可以看到,两个进程中显示的内存地址并不一样,这里有什么原因呢?查阅资料,可能有下面的原因
- 现代操作系统中都存在ASLR(地址空间随机化),ASLR是⼀种针对缓冲区溢出的安全保护机制,具有ASLR机制的操作系统每次加载到内存的程序起始地址会随机变化。系统的这个随机化操作可能导致共享内存的地址不一致。
- 进程在挂载内存的时候使用的shmat()函数中的第二个参数使用的是NULL,NULL参数的含义是进程让系统分配给共享内存合适的地址。并没有直接给shmat()函数一个确切的地址,所以会导致地址的不一致。
2 管道通信
实验题目
有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
实验代码
无名管道
pipe.c
#include <stdio.h>
#include <unistd.h> //for pipe()
#include <string.h> //for memset()
#include <stdlib.h> //for exit()
int main()
{
int fd[2];
char buf[20];
if(-1 == pipe(fd))
{
perror("pipe");
exit(EXIT_FAILURE);
}
write(fd[1], "hello,world", 12);
memset(buf, '\0', sizeof(buf));
read(fd[0], buf, 12);
printf("The message is: %s\n", buf);
return 0;
}
有名管道
fifo_send.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <fcntl.h>
#define FIFO "./my_fifo"
int main()
{
char buf[] = "hello,world";
//1. check the fifo file existed or not
int ret;
ret = access(FIFO, F_OK);
if(ret != 0) //file /tmp/my_fifo existed
{
if(-1 == mkfifo(FIFO, 0766))
{
perror("mkfifo");
exit(EXIT_FAILURE);
}
}
//3.Open the fifo file
int fifo_fd;
fifo_fd = open(FIFO, O_WRONLY);
if(-1 == fifo_fd)
{
perror("open");
exit(EXIT_FAILURE);
}
//4. write the fifo file
int num = 0;
num = write(fifo_fd, buf, sizeof(buf));
if(num < sizeof(buf))
{
perror("write");
exit(EXIT_FAILURE);
}
printf("write the message ok!\n");
close(fifo_fd);
return 0;
}
fifo_rcv.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 "./my_fifo"
int main()
{
char buf[20] ;
memset(buf, '\0', sizeof(buf));
//`. check the fifo file existed or not
int ret;
ret = access(FIFO, F_OK);
if(ret != 0) //file /tmp/my_fifo existed
{
if(-1==mkfifo(FIFO,0766))
{
perror("mkfifo");
exit("EXIT_FAILURE");
}
}
// 2.Open the fifo file
int fifo_fd;
fifo_fd = open(FIFO, O_RDONLY);
if(-1 == fifo_fd)
{
perror("open");
exit(EXIT_FAILURE);
}
//4. read the fifo file
int num = 0;
num = read(fifo_fd, buf, sizeof(buf));
printf("Read %d words: %s\n", num, buf);
close(fifo_fd);
return 0;
}
实验结果及分析
无名管道
经过查阅资料,得到父子进程间利用无名管道同步通信的机理如下:
- 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
- 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
- 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
对pipe.c进行修改(pipe2.c),父进程将用户输入的信息不断写入管道,子进程不断将管道中的信息输出,如果内存被读进程锁定,父进程就会阻塞。如果内存被读进程锁定,同时若管道中无数据,子进程就会阻塞。
有名管道
无论首先运行哪一个进程,它都会阻塞等待,只有当另一个进程开始运行后,两个进程才能正常执行。
试验有名管道的阻塞机制
编写四段程序,fifo_send.c(写阻塞),fifo_rcv.c(读阻塞),fifo_send2.c(写非阻塞),fifo_rcv2.c(读非阻塞)
(1)写进程阻塞,读进程阻塞。(如上)
先运行写进程(被阻塞),再运行读进程,一切正常。
先运行读进程(被阻塞),再运行写进程,一切正常。
(2)写进程阻塞,读进程非阻塞。
先运行写进程(被阻塞),再运行读进程,一切正常。
先运行读进程,程序崩溃(Segmentation fault (core dumped)),是由不等待直接读造成的。
(3)写进程非阻塞,读进程阻塞。
先运行写进程,open调用将返回-1,打开失败。
先运行读进程(被阻塞),再运行写进程,一切正常。
(4)读写进程都是非阻塞
总的来说,引起读进程阻塞的原因有,读进程阻塞的原因有三种:FIFO 中没有数据、有其他的读进程正在读取这些数据、没有写进程打开FIFO文件,对于写进程而言,阻塞的原因是管道中的空闲位置不足以容纳要写入的数据,泽写进程阻塞,直到管道中空间足够,一次性写入所有数据。
3 消息队列
实验题目
消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
实验代码
Client.c
#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
//Rebuild the strcut (must be)
struct msgbuf
{
long mtype;
char mtext[BUF_SIZE];
};
int main(int argc, char *argv[])
{
//1. creat a mseg queue
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);
if(-1 == msgId)
{
perror("msgget");
exit(EXIT_FAILURE);
}
//2. creat a sub process, wait the server message
pid_t pid;
if(-1 == (pid = fork()))
{
perror("vfork");
exit(EXIT_FAILURE);
}
//In child process
if(0 == pid)
{
while(1)
{
alarm(0);
alarm(100); //if doesn't receive messge in 100s, timeout & exit
struct msgbuf rcvBuf;
memset(&rcvBuf, '\0', sizeof(struct msgbuf));
msgrcv(msgId, &rcvBuf, BUF_SIZE, 2, 0);
printf("Server said: %s\n", rcvBuf.mtext);
}
exit(EXIT_SUCCESS);
}
else //parent process
{
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))
{
perror("msgsnd");
exit(EXIT_FAILURE);
}
//if scanf "end~", exit
if(!strcmp("end~", buf))
break;
}
printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());
}
return 0;
}
Server.c
#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
//Rebuild the strcut (must be)
struct msgbuf
{
long mtype;
char mtext[BUF_SIZE];
};
int main(int argc, char *argv[])
{
//1. creat a mseg queue
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); //if doesn't receive messge in 600s, timeout & exit
struct msgbuf rcvBuf;
memset(&rcvBuf, '\0', sizeof(struct msgbuf));
msgrcv(msgId, &rcvBuf, BUF_SIZE, 1, 0);
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))
{
perror("msgsnd");
exit(EXIT_FAILURE);
}
//if scanf "end~", exit
if(!strcmp("end~", rcvBuf.mtext))
break;
}
printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());
return 0;
}
实验结果及分析
客户端的子进程主要负责消息的接受,父进程主要负责消息的发送,客户端和服务器端都是以阻塞的方式读取和写入消息.
试验消息队列的同步和阻塞机制
消息队列通过msgrcv和msgsnd两个函数的flag参数控制是否阻塞,将其设置为IPC_NOWAIT表示不阻塞;如果客户端和服务器端都设置阻塞的话,就可以达到同步的目的
编写四段程序,client.c(阻塞),server.c(阻塞),client1.c(非阻塞),server1.c(非阻塞)
(1)客户端阻塞,服务器端不阻塞
我们可以看到当服务器端没有设置阻塞的时候,服务器端会一直接受消息队列中的空消息并向客户端转发。
(2)客服端不阻塞,服务器端阻塞
我们可以看到当客户端不阻塞时,在客户端会无限制的打印消息队列中的空消息,即使消息队列中没有任何消息
五
实验题目
阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。
分析
首先,我们找到关于进程上下文切换的函数timer_sleep(),它在devices/timer.c这个文件中
/* Sleeps for approximately TICKS timer ticks. Interrupts must
be turned on. */
void timer_sleep (int64_t ticks)
{
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
while (timer_elapsed (start) < ticks)
thread_yield ();
}
在timer_sleep()这个函数中,我们来一行一行分析,我们遇到了第一个函数timer_ticks ()
/* Returns the number of timer ticks since the OS booted. */
int64_t timer_ticks (void)
{
enum intr_level old_level = intr_disable ();
int64_t t = ticks;
intr_set_level (old_level);
return t;
}
在timer_ticks ()这个函数中,我们又遇到了函数intr_disable(),我们去查看这个函数的定义
/* Disables interrupts and returns the previous interrupt status. */
enum intr_level intr_disable (void)
{
enum intr_level old_level = intr_get_level ();
/* Disable interrupts by clearing the interrupt flag.
See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable
Hardware Interrupts". */
asm volatile ("cli" : : : "memory");
return old_level;
}
那么,intr_level又是什么呢?它的定义在devices/interrupt.h中
enum intr_level
{
INTR_OFF, /* Interrupts disabled. */
INTR_ON /* Interrupts enabled. */
};
我们可以看出,intr_level表示是否允许中断,那么我们返回上一层intr_disable,它调用了intr_old_level函数,然后执行汇编代码保证这个线程不能被中断。之后返回调用intr_old_level函数的返回值。
我们继续看看*intr_get_level()*这个函数的作用是什么。
/* Returns the current interrupt status. */
enum intr_level intr_get_level (void)
{
uint32_t flags;
/* Push the flags register on the processor stack, then pop the
value off the stack into `flags'. See [IA32-v2b] "PUSHF"
and "POP" and [IA32-v3a] 5.8.1 "Masking Maskable Hardware
Interrupts". */
asm volatile ("pushfl; popl %0" : "=g" (flags));
return flags & FLAG_IF ? INTR_ON : INTR_OFF;
}
可以看出*intr_get_level()*这个函数的作用是返回当前的中断状态,返回上一层intr_disable()函数。它的作用是,获取当前中断状态,然后将当前的中断标志改为不可中断,最后返回之前的中断状态。
返回*timer_ticks()*函数,64位的整型变量t为ticks,然后将当前的中断状态设置为之前的中断状态,最后返回t值。
最后返回*timer_sleep()*函数,64位的整型变量start变相获得了ticks的值,然后将当前的中断状态置为开,当start小于ticks后,放弃CPU,强迫线程切换。