实验目的
- 系统调用的进一步理解。
- 进程上下文切换。
- 同步与通信方法。
实验题目
一
通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
- 以下为fock.c代码
//fock.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<fcntl.h>
int main()
{
sem_t *P1_signal,*P2_signal,*P3_signal,*P23_signal;
//主函数中的进程是P1
pid_t p2,p3,p4;
//创建信号量
P1_signal=sem_open("P1_signal",O_CREAT,0666,1);
P2_signal=sem_open("P2_signal",O_CREAT,0666,0);
P3_signal=sem_open("P3_signal",O_CREAT,0666,0);
sem_wait(P1_signal);
p2=fork();//创建进程P2
if(p2==0)
{
sem_wait(P1_signal); //等待p1
printf("I am the process P2!\n");
sem_post(P2_signal);
sem_post(P1_signal);
}
if(p2>0)
{
p3=fork();
if(p3==0)
{
sem_wait(P1_signal);
printf("I am the process P3!\n");
sem_post(P3_signal);
sem_post(P1_signal);
}
if(p3>0)
{
printf("I am the process P1!\n");
sem_post(P1_signal);
p4=fork();
if(p4==0)
{
sem_wait(P2_signal);
sem_wait(P3_signal);
sem_wait(P1_signal);
printf("I am the process P4!\n");
sem_post(P2_signal);
sem_post(P3_signal);
sem_post(P1_signal);
}
}
}
sem_close(P1_signal);
sem_close(P23_signal);
sem_close(P2_signal);
sem_close(P3_signal);
sem_unlink("P1_signal");
sem_unlink("P23_signal");
sem_unlink("P2_signal");
sem_unlink("P3_signal");
sleep(0.5);
return 0;
}
- 由以下截图可证明:进程p2和p3互斥。
多次试验满足:P1最先执行,P2、P3互斥执行,P4最后执行。
二
火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。
- 添加同步机制前代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
volatile int ticketCount=1000;
void *SaleThread(void *arg)
{
int num,temp;
num=1000;
for(int i=0;i<num;i++)
{
if(i % 200 ==0)
printf("卖%d张票,剩余%d张票\n",i,ticketCount);
temp=ticketCount;
//放弃CPU,强制切换到另外一个进程
pthread_yield();
temp=temp-1;
ticketCount-=1;
pthread_yield();
ticketCount=temp;
}
return NULL;
}
void *RefundThread(void *arg)
{
int num,temp;
num=1000;
for(int i=0;i<num;i++)
{
if(i % 200 ==0)
printf("退%d张票,剩余%d张票\n",i,ticketCount);
temp=ticketCount;
pthread_yield();
temp=temp+1;
ticketCount+=1;
pthread_yield();
ticketCount=temp;
pthread_yield();
}
return NULL;
}
int main(int argc,char *argv[])
{
printf("初始票数为:%d\n",ticketCount);
pthread_t p1,p2;
pthread_create(&p1,NULL,SaleThread,argv[1]);
pthread_create(&p2,NULL,RefundThread,argv[2]);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
printf("最终票数为:%d\n",ticketCount);
return 0;
}
- 不同步时火车购票情况如下
由下图可知,不同步时买票与退票不同步,剩余票数不一定与初始票数一致。
- 添加同步机制
sem_wait(flag);
pthread_yield();
sem_post(flag);
- 添加同步机制后购票情况
可以看出卖票与退票数一致
- 以下为同步后代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
volatile int ticketCount=1000;
sem_t *flag=NULL;
//买票
void *SaleThread(void *arg)
{
int num,temp;
num=1000;
for(int i=0;i<num;i++)
{
//每200张输出一次
if(i % 200 ==0)
printf("卖%d张票,剩余%d张票\n",i,ticketCount);
sem_wait(flag);
temp=ticketCount;
pthread_yield();//放弃CPU,切换到另一进程
temp=temp-1;
ticketCount-=1;
pthread_yield();
ticketCount=temp;
pthread_yield();
sem_post(flag);
}
return NULL;
}
//退票
void *RefundThread(void *arg)
{
int num,temp;
num=1000;
for(int i=0;i<num;i++)
{
//每200张输出一次
if(i % 200 ==0)
printf("退%d张票,剩余%d张票\n",i,ticketCount);
sem_wait(flag);
temp=ticketCount;
pthread_yield();
temp=temp+1;
ticketCount+=1;
pthread_yield();
ticketCount=temp;
pthread_yield();
sem_post(flag);
}
return NULL;
}
int main(int argc,char *argv[])
{
flag=sem_open("flag",O_CREAT,0666,1);
printf("初始票数为:%d\n",ticketCount);
pthread_t p1,p2;
pthread_create(&p1,NULL,SaleThread,argv[1]);
pthread_create(&p2,NULL,RefundThread,argv[2]);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
printf("最终票数为:%d\n",ticketCount);
sem_close(flag);
sem_unlink("flag");
return 0;
}
三
一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。
实验代码如下
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/stat.h>
#include<fcntl.h>
char buf[10];
sem_t *empty=NULL;
sem_t *full=NULL;
void *worker1(void *arg)
{
for(int i=0;i<10;i++)
{
sem_wait(empty);
/* fflush(stdin); */
/* printf("输入:"); */
scanf("%c",&buf[i]);
sem_post(full);
if(i==9)
{
i=-1;
}
}
return NULL;
}
void *worker2(void *arg)
{
for(int i=0;i<10;i++)
{
sem_wait(full);
printf("输出:%c\n",buf[i]);
sem_post(empty);
sleep(1);
if(i==9)
{
i=-1;
}
}
return NULL;
}
int main(int argc,char *argv[])
{
empty=sem_open("empty_",O_CREAT,0666,10);
full=sem_open("full_",O_CREAT,0666,0);
pthread_t p1,p2;
pthread_create(&p1,NULL,worker1,NULL);
pthread_create(&p2,NULL,worker2,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
sem_close(empty);
sem_close(full);
sem_unlink("empty_");
sem_unlink("full_");
return 0;
}
- 在上一次输出完成之前,再次输入,输出正确
- 两次输入和超出缓冲区,输出正确
- 单次输入超出缓冲区,输入正确
四
进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码
-
通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?
可以。由下图可知receiver正确接收了sender
如果把其中互斥的代码删除,观察实验结果有何不同?
(为防止while(1)输出,改为每隔1s循环一次):
去同步后代码sender2
/*
* Filename: Sender.c
* Description:
*/
#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, snd message process running:\n");
printf("\tThe shared pointer address : %p\n", shm_ptr);
printf("\tInput the snd message: ");
scanf("%s", shm_ptr);
if(0)// == 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;
}
去同步后代码receiver2
/*
* Filename: Receiver.c
* Description:
*/
#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);
printf("\tThe shared pointer address : %p\n", shm_ptr);
if(0)// == 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;
}
sleep(1);
}
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:
接收了消息。 -
消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
运行client和server:
输入信息可正常发送并收到回复只运行client并发送消息:
可见发送端不会被阻塞再运行server:
会将已有内容依次处理,之后由于消息队列中无内容而被阻塞。
五
阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。
pintos在thread.h中定义了一个结构体struct thread,这个结构体描述了有关进程的基本信息。
struct thread
{
tid_t tid; /* Thread identifier. */
enum thread_status status; /* Thread state. */
char name[16]; /* Name (for debugging purposes). */
uint8_t *stack; /* Saved stack pointer. */
int priority; /* Priority. */
struct list_elem allelem; /* List element for all threads list. */
/* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */
#ifdef USERPROG
/* Owned by userprog/process.c. */
uint32_t *pagedir; /* Page directory. */
#endif
/* Owned by thread.c. */
unsigned magic; /* Detects stack overflow. */
};
enum thread_status
{
THREAD_RUNNING, /* Running thread. */
THREAD_READY, /* Not running but ready to run. */
THREAD_BLOCKED, /* Waiting for an event to trigger. */
THREAD_DYING /* About to be destroyed. */
};
中断分两种,一种是IO设备向CPU发出的中断的信息,另一种是CPU决定切换到另一个进程时(轮换时间片)发出的指令。我们现在处理第二种。pintos的中断在interrupt.h和interrupt.c之中。其中这个枚举类型intr_lverl会在后面被反复提到:
enum intr_level
{
INTR_OFF, /* Interrupts disabled. */
INTR_ON /* Interrupts enabled. */
};
其中intr_off表示关中断,on表示开中断。执行原子级别操作的时候,中断必须是关闭状态。
pintos是以ticks作为基本时间单位的,每秒有TIMER_FREQ个ticks:
/* Number of timer interrupts per second. */
#define TIMER_FREQ 100 //系统默认这个宏为100
pintos默认每一个ticks调用一次时间中断。每一个线程最多可以占据CPU一个ticks的时长,之后就必须切换。
与thread有关的函数,以下函数在thread.c中都可以找到:
1.thread_current()获取当前当前的线程的指针。
2.thread_foreach(thread_action_func *func, void *aux) 遍历当前ready queue中的所有线程,并且对于每一个线程执行一次func操作。这里的func是一个任意给定函数的指针,参数aux则是想要传给这个函数的参数。
3.thread_block()和thread_unblock(thread *t)。 这是一对函数,区别在于第一个函数的作用是把当前占用cpu的线程阻塞(就是放到waiting里面),而第二个函数作用是将已经被阻塞的进程t唤醒到ready队列中。
4.timer_interrupt (struct intr_frame *args UNUSED)在timer.c中,pintos在每次时间中断时(即每一个时间单位(ticks))调用一次这个函数。
5.intr_disable ()在interrupt.c中,返回关中断,然后返回中断关闭前的状态。