16281053-杨瑷彤-操作系统实验三-同步与通信
github代码链接:https://github.com/rdjyat/operating-system/tree/master/操作系统实验三
1. 实验目的
系统调用的进一步理解。
进程上下文切换。
同步与通信方法。
2. 基础知识
2.1 信号量
Linux提供了一组精心设计的信号量接口来对信号进行操作,它们不只是针对二进制信号量,下面将会对这些函数进行介绍,但请注意,这些函数都是用来对成组的信号量值进行操作的。它们声明在头文件sys/sem.h中。
——semget函数
它的作用是创建一个新信号量或取得一个已有信号量,原型为:
int semget(key_t key, int num_sems, int sem_flags);
第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。
第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-1.
2.2 线程控制
linux系统支持POSIX多线程接口,称为pthread。编写linux下的多线程程序,需要包含头文件pthread.h。如果在主线程里面创建线程,程序就会在创建线程的地方产生分支,变成两个部分执行。线程的创建通过函数pthread_create来完成。成功返回0
线程创建
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
thread: 参数是一个指针,当线程成功创建时,返回创建线程ID。
attr: 用于指定线程的属性。
start_routine: 该参数是一个函数指针,指向线程创建后要调用的函数。
arg: 传递给线程函数的参数。
等待线程结束
int pthread_join(pthread_t thread, void **retval);
返回值:若是成功建立线程返回0,否则返回错误的编号
形 参:thread 被等待的线程标识符
retval 一个用户定义的指针,它可以用来存储被等待线程的返回值
说 明:这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回
头文件:#include <pthread.h>
线程终止
void pthread_exit(void* retval);
返回值:无
形 参:retval 函数的返回指针,只要pthread_join中的第二个参数retval不是NULL,这个值将被传递给retval
说 明:终止调用它的线程并返回一个指向某个对象的指针。
头文件:#include <pthread.h>
实验内容
第一题
通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
解答
分析:
1)由题意,设计四个进程P1-P4,信号量S1-S3,其中关系如下。
用信号量S1完成P1与P2的先后关系,P1与P3的先后关系,以及P2与P3的互斥关系;用S2完成P2与P4的先后关系;用S3完成P3与P4的先后关系。
2)自定义四个子函数,sem_init、sem_del、wait、signal用于对题中信号量的处理。
main函数中调用semget系统调用,用来获取信号量
sem_init/del中调用 semctl系统调用,用来控制信号量
Wait/signal中调用semop系统调用,用来操作信号量
运行结果
代码:
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
union semun{
/*定义联合体*/
int val;
struct semid_ds*buf;
unsigned short *array;
};
/*函数声明*/
int init_sem(int sem_id,int init_value);///*信号量初始化函数*/
int del_sem(int sem_id); /*删除信号量函数*/
int wait(int sem_id); /*wait函数*/
int signal(int sem_id); /*signal函数*/
int init_sem(int sem_id,int init_value){
/*信号量初始化函数*/
union semun sem_union;
sem_union.val = init_value; //init_value为初始值
if(semctl(sem_id,0,SETVAL,sem_union) == -1){ //调用semctl函数为信号量赋初值
perror("Initialize semaphore");
return -1;
}
return 0;
}
int del_sem(int sem_id){
/*删除信号量函数*/
union semun sem_union;
if(semctl(sem_id,0,IPC_RMID,sem_union) == -1){
perror("Delete semaphore");
return -1;
}
return 0;
}
int wait(int sem_id){
/*P操作函数*/
struct sembuf sem_b;
sem_b.sem_num = 0; //信号量编号,这里单个信号的编号应该为0
sem_b.sem_op = -1; //信号量操作,取值为-1表示P操作
sem_b.sem_flg = SEM_UNDO; //在进程没释放信号量而退出时,系统自动释放该进程中未释放的信号量
if(semop(sem_id, &sem_b, 1) == -1){ //进行P操作
perror("P operation");
return -1;
}
return 0;
}
int signal(int sem_id){
/*V操作函数*/
struct sembuf sem_b;
sem_b.sem_num = 0; //信号量编号,这里单个信号的编号应该为0
sem_b.sem_op = 1; //信号量操作,取值为+1表示V操作
sem_b.sem_flg = SEM_UNDO; //在进程没释放信号量而退出时,系统自动释放该进程中未释放的信号量
if(semop(sem_id, &sem_b, 1) == -1){ //进行V操作
perror("V operation");
return -1;
}
return 0;
}
int main(){
pid_t p1,p2,p3,p4;
int s1,s2,s3;
/*调用semget函数,创建三个信号量*/
s1 = semget(ftok(".",'a'),1,0666|IPC_CREAT);
init_sem(s1,0);
s2 = semget(ftok(".",'b'),1,0666|IPC_CREAT);
init_sem(s2,0);
s3 = semget(ftok(".",'c'),1,0666|IPC_CREAT);
init_sem(s3,0);
/*调用fork函数,创建四个进程*/
p1 = fork();
if(!p1){ //子进程p1
printf("I'm the process p1 with PID=%d\n",getpid());
signal(s1);
exit(0);
}
p2 = fork();
if(!p2){ //子进程p2
wait(s1);
printf("I'm the process p2 with PID=%d\n",getpid());
signal(s2);
signal(s1);
sleep(1);
exit(0);
}
p3 = fork();
if(!p3){ //子进程p3
wait(s1);
printf("I'm the process p3 with PID=%d\n",getpid());
signal(s3);
signal(s1);
sleep(1);
exit(0);
}
p4 = fork();
if(!p4){ //子进程p4
wait(s2);
wait(s3);
printf("I'm the process p4 with PID=%d\n",getpid());
signal(s2);
signal(s3);
//del_sem(s1);
//del_sem(s2);
//del_sem(s3);
exit(0);
}
exit(0);
}
运行结果
如下图,运行得到正确结果。
测试
1、注释掉P1中的signal(S1),则P2P3P4都不会执行。
2、注释掉P3中的signal(S3),则P4不会执行。
3、注释掉P1中的signal(S1)及P4中的wait(S2)、wait(S3),则P1P4 执行,P2P3不执行。
由此,经过测试可以验证正确性。
第二题
火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。
解答
根据题目以及老师给的关键代码,编写程序如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <sched.h>
sem_t* mySem = NULL;
int tickets = 1000;
void *sell_ticket(){
int i = 50;
while(i--){
sem_wait(mySem);
printf("当前票数为 %d\n",tickets);
int temp = tickets;
sched_yield();
temp = temp - 1;
sched_yield();
tickets = temp;
sem_post(mySem);
}
}
void *return_ticket(){
int i = 50;
while(i--){
sem_wait(mySem);
printf("当前票数为 %d\n",tickets);
int temp = tickets;
sched_yield();
temp = temp + 1;
sched_yield();
tickets = temp;
sem_post(mySem);
}
}
int main(){
pthread_t p1,p2;
mySem = sem_open("Ticket", O_CREAT, 0666, 1);//改变信号量的值
pthread_create(&p1,NULL,sell_ticket,NULL);//创建卖票线程
pthread_create(&p2,NULL,return_ticket,NULL);//创建退票线程
pthread_join(p1,NULL);//等待线程
pthread_join(p2,NULL);
sem_close(mySem);
sem_unlink("Ticket");
printf("最终票数为 %d.\n",tickets);
return 0;
}
使售票和退票线程各运行50次,结果正确,如下截图:
可见结果正确。
分析:sched_yield()这个函数可以使用另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。所以在售票以及退票进程中,在对temp值进行更改前后需要调用sched_yield()函数使当前进程让出cpu使用权。
第三题
一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。
解答
根据题目,编写代码如下:
#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};
sem_t empty; //信号量1,缓存区中空闲单元的个数
sem_t datas; //信号量2,缓存区中非空单元的个数
void *producer(void *arg){
for (int i = 0; i < 10;) {
sem_wait(&empty); //判断是否有空闲单元可供输入
scanf("%c",&buf[i]);
i++;
i = i%10;
sem_post(&datas); //输入一个数据,非空单元数+1
}
return NULL;
}
void *consumer(void *arg){
for (int i = 0; i < 10;) {
sem_wait(&datas);
printf("%c",buf[i]);
sleep(1);
i++;
i=i%10;
sem_post(&empty); //输出一个数据,空闲单元数+1
}
return NULL;
}
int main(int argc, char *argv[]){
sem_init(&empty, 0, 10);//初始化信号量
sem_init(&datas, 0, 0);
pthread_t p1, p2;
pthread_create(&p1, NULL, producer, NULL); //创建线程
pthread_create(&p2, NULL, consumer, NULL);
pthread_join(p1, NULL);//线程等待
pthread_join(p2, NULL);
sem_destroy(&empty); //销毁线程
sem_destroy(&datas);
return 0;
}
测试输入为我自己的学号和姓名,运行结果如下:
分析:打印输出进程需要字符缓冲区有新的字符输入,以免重复输出已经输出的字符。所以初始的输出信号量是0,每次读入一个字符后会加1。使用两个信号量进行进程同步,分别表示空闲资源和非空单元,同步机制避免了数据丢失,即不会出现输入过快而导致的数据覆盖。
第四题
进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码
(参见
https://www.cnblogs.com/Jimmy1988/p/7706980.html
https://www.cnblogs.com/Jimmy1988/p/7699351.html
https://www.cnblogs.com/Jimmy1988/p/7553069.html )
解答
a)通过实验测试,验证共享内存的代码中,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;
}
将链接中的代码建立.c文件,并运行进行验证,结果如下:
由此可见,receiver可以正确读出sender发送的字符串。
删除互斥代码后,receiver没有互斥信号量的限制,在循环中快速重复输出最近一次sender输入的内容,如下图:
输出内存地址,截图如下
可见,内存地址并不相同。
b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
无名管道为单向阻塞读写,但多进程使用同一管道通信时容易造成交叉读写的问题。一个管道实际上就是一个只存在于内存中的文件,对这个文件的操作需要通过两个已经打开的文件进行,它们分别代表管道的两端。无名管道通过pipe()函数创建。为了克服无名管道的不足(只能用于具有亲缘关系的进程如父子进程),有名管道FIFO被提出,它可以使得互不相关的两个进程互相通信。FIFO也属于单向进程通信,当通信双方进程一方不存在时则阻塞。
无名管道代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
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;
}
有名管道
send
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <fcntl.h>
#define FIFO "/tmp/my_fifo"
int main()
{
char buf[] = "hello,world";
int ret;
ret = access(FIFO, F_OK);
if(ret == 0) //file /tmp/my_fifo existed
{
system("rm -rf /tmp/my_fifo");
}
//2. creat a fifo file
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;
}
receive
#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 "/tmp/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
{
fprintf(stderr, "FIFO %s does not existed", FIFO);
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中写入数据前,子进程原地等待(阻塞);当父进程的写操作执行完毕后,子进程才能将pipe内的数据输出。
c)消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
代码如下:
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;
}
运行结果:
分析:消息队列是消息的链表,存放在系统内核中并由消息队列标识符表示,它提供了一个从一个进程向另一个进程发送不同类型数据块的方法。同pipeline类似,每个消息的最大长度MSGMAX是有上限的,所以消息队列可存放的数据是有限的。消息队列支持双向通信,克服了管道只能承载无格式字节流的缺点,在进程间只有少量数据传输的前提下实现了同步机制。所以,当消息队列已满时,发送进程将会被阻塞。
第五题
阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。
解答
进程切换就是从正在运行的进程中收回处理器,然后再使待运行进程来占用处理器。所谓从某个进程收回处理器,实质上就是将此进程存放在处理器的寄存器中的数据暂时存储起来,从而让其他进程使用寄存器。每个进程都会分到CPU的时间片来运行,当一个进程用完时间片或者被更高优先级的进程抢占后,它会备份到CPU的运行队列中,同时其他进程在CPU上运行。这个进程切换的过程被称作上下文切换。
pintos中实现上下文切换的代码如下:
static void schedule (void)
{
struct thread *cur = running_thread ();
struct thread *next = next_thread_to_run ();
struct thread *prev = NULL;
ASSERT (intr_get_level () == INTR_OFF);
ASSERT (cur->status != THREAD_RUNNING);
ASSERT (is_thread (next));
if (cur != next)
prev = switch_threads (cur, next);
thread_schedule_tail (prev);
}
其中,cur表示当前线程的PCB起始地址,next表示下一个要执行的线程的PCB起始地址,prev表示前一个线程的PCB起始地址。
static struct thread *
next_thread_to_run (void)
{
if (list_empty (&ready_list))
return idle_thread;
else
return list_entry (list_pop_front (&ready_list), struct thread, elem);
}
这个函数的功能是保存当前线程的状态,并恢复已保存的线程状态。