Task 1:
实验要求:
通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
程序代码:
#include
#include
#include
#include
#include
sem_t *mySem = NULL; //进程2和进程3互斥信号量
sem_t *mySem2 = NULL; //进程2结束信号量
sem_t *mySem3 = NULL; //进程3结束信号量
void *createP2(void *arg) {
pid_t p2;
sem_wait(mySem);
while((p2=fork())==-1);
if(p2>0)
printf("I am the process P2!\n");
sem_post(mySem);
sem_post(mySem2);
return NULL;
}
void *createP3(void *arg) {
pid_t p3;
sem_wait(mySem);
while((p3=fork())==-1);
if(p3>0)
printf("I am the process P3!\n");
sem_post(mySem);
sem_post(mySem3);
return NULL;
}
void *createP4(void *arg) {
pid_t p4;
sem_wait(mySem2);
sem_wait(mySem3);
while((p4=fork())==-1);
if(p4>0)
printf("I am the process P4!\n");
sem_post(mySem2);
sem_post(mySem3);
return NULL;
}
int main(int argc, char* argv[])
{
pid_t p1;
pthread_t pp2, pp3, pp4;
while((p1=fork())==-1);
if(p1>0){
printf("I am the process P1!\n");
mySem = sem_open("mySem", O_CREAT, 0666, 1);
mySem2 = sem_open("mySem2", O_CREAT, 0666, 0);
mySem3 = sem_open("mySem3", O_CREAT, 0666, 0);
pthread_create(&pp2, NULL, createP2, NULL);
pthread_create(&pp3, NULL, createP3, NULL);
pthread_create(&pp4, NULL, createP4, NULL);
pthread_join(pp2, NULL);
pthread_join(pp3, NULL);
pthread_join(pp4, NULL);
sem_close(mySem); //线程结束
sem_close(mySem2);
sem_close(mySem3);
sem_unlink("mySem");
sem_unlink("mySem2");
sem_unlink("mySem3");
}
return 0;
}
代码解析:
主函数产生进程P1,在进程P1中通过三个线程PP2、PP3、PP4分别执行子函数createP2、createP3、createP4产生3个进程P2、P3、P4。
设置三个信号量: mySem(进程2和进程3互斥信号量)、mySem2(进程2结束信号量)、mySem3(进程3结束信号量)用来实现P2、P3互斥执行,P4最后执行。
实验结果:
代码执行结果顺序为:P1、P3、P2、P4,符合实验要求。
原理解析:
进程前趋图为:
P1为在主函数中直接通过过fork的方式产生,最先执行;
void *createP2(void *arg) {
pid_t p2;
sem_wait(mySem);
while((p2=fork())==-1);
if(p2>0)
printf("I am the process P2!\n");
sem_post(mySem);
sem_post(mySem2);
return NULL;
}
void *createP3(void *arg) {
pid_t p3;
sem_wait(mySem);
while((p3=fork())==-1);
if(p3>0)
printf("I am the process P3!\n");
sem_post(mySem);
sem_post(mySem3);
return NULL;
}
由上述子函数可知: 两个线程同时阻塞等待sem_wait(mySem),则P2、P3互斥执行;
void *createP4(void *arg) {
pid_t p4;
sem_wait(mySem2);
sem_wait(mySem3);
while((p4=fork())==-1);
if(p4>0)
printf("I am the process P4!\n");
sem_post(mySem2);
sem_post(mySem3);
return NULL;
}
由子函数createP4可知: 必须等待sem_wait(mySem2)、sem_wait(mySem3);都实现后,才可创建进程P4,P4最后执行。
Task 2:
实验要求:
火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。(说明:为了更容易产生并发错误,可以在适当的位置增加一些pthread_yield(),放弃CPU,并强制线程频繁切换。)
程序代码:
#include
#include
#include
#include
#include
#include
int pthread_yield(void);
int ticketCount = 1000;
sem_t *mySem = NULL;
void *ticket(void *arg) { //售票
int temp;
while(1){
if(ticketCount>0){
sem_wait(mySem); //同步操作
temp=ticketCount;
pthread_yield();
temp=temp-1;
pthread_yield();
ticketCount=temp;
sleep(1);
printf("售票1张,现有票数:%d!\n",ticketCount);
sem_post(mySem); //同步操作
}
}
return NULL;
}
void *refund(void *arg) { //退票
int temp;
while(1){
if(ticketCount<1000){
sem_wait(mySem); //同步操作
temp=ticketCount;
pthread_yield();
temp=temp+1;
pthread_yield();
ticketCount=temp;
sleep(1);
printf("退票1张,现有票数:%d!\n",ticketCount);
sem_post(mySem); //同步操作
}
}
return NULL;
}
int main(void)
{
pthread_t p1, p2;
printf("现有票数:%d张,售票退票线程开始!\n",ticketCount);
mySem = sem_open("mySem", O_CREAT, 0666, 1);
pthread_create(&p1, NULL, ticket, NULL);
pthread_create(&p2, NULL, refund, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
sem_close(mySem);//线程结束
sem_unlink("mySem");//主程序结尾
return 0;
}
代码解析:
注:
带有“//同步操作”的程序代码行为实现同步机制所添加,在不想要同步前应注释掉;
主函数产生两个线程P1、P2分别执行子函数ticke(售票)、refund(退票)用来售票、退票操作。程序代码应注意:在没有售出票以前,不允许退票。在售票1000张后,不在允许售票。售票、退票两个线程同时执行,并在适当的位置增加一些pthread_yield(),放弃CPU,强制线程频繁切换。
通过设置信号量mySem用来实现同步机制,即不能同时对余票量ticketCount进行写操作。
实验结果解析:
添加同步前,两个线程同时执行,当售票一张后,还未打印余票量ticketCount时,退票线程又退票一张,使得余票量ticketCount又加一,打印余票量时ticketCount为退票执行后,则打印出结果为:售票1张,现有票数:1000!;明显结果错误!因此,未添加同步前,两个线程可同时对余票量ticketCount进行读写操作,则导致显示结果不可信,数据不准确!
添加同步后,两个线程共享信号量mysem,互斥执行,即当售票时,退票阻塞等待;即当退票时,售票阻塞等待。只有当售票完全结束后,余票量ticketCount变化后,打印余票量ticketCount后,才可进行下一线程操作;退票时也相同。因此,添加同步后,两个线程不可同时对余票量ticketCount进行读写操作,则数据具有准确性!退票,售票进程是处理机随机调度,不具有可预测性!当退票数达到1000后,不可在进行退票操作,符合现实情况,待有票售出后,才可继续进行退票操作(结果演示请看添加同步后第3张截图)。
Task3:
实验要求:
一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。
程序代码:
#include
#include
#include
#include
#include
#include
#include
char buffer[10];
int i=0,j=0;
sem_t *full = NULL;
sem_t *empty = NULL;
void *worker1(void *arg) { //生产者
while(1){
sem_wait(empty);
scanf("%c",&buffer[j]);j++;
sleep(1);
if(j>=10)j%=10;
sem_post(full);
}
return NULL;
}
void *worker2(void *arg) { //消费者
while(1){
sem_wait(full);
printf("输出:%c\n",buffer[i]);i++;
sleep(1);
if(i>=10)i%=10;
sem_post(empty);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t p1, p2;
full = sem_open("full", O_CREAT, 0666, 0);
empty = sem_open("empty", O_CREAT, 0666, 10);
pthread_create(&p1, NULL, worker1, NULL);
pthread_create(&p2, NULL, worker2, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
sem_close(full);//线程结束
sem_close(empty);//线程结束
sem_unlink("full");//主程序结尾
sem_unlink("empty");//主程序结尾
return 0;
}
代码解析:
主函数产生两个线程P1、P2分别执行子函数worker1(生产者)、worker2(消费者)用来往buffer缓冲区中读入、读出数据操作。设置一个共享信号量full,用来表示buffer缓冲区中数据个数;设置一个共享信号量empty,用来表示buffer缓冲区中空闲位置个数;
需注意:在没有数据以前,不允许读出,worker2()函数阻塞等待;在数据存满以后,不允许读入,worker1()函数阻塞等待。
实验结果:
第一次测试:
第二次测试:
第三次测试(输入数据大于10):
第四次测试(连续输入):
实验结果解析:
一个生产者一个消费者线程共享的一个缓冲区:char buf[10];一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。在实验结果截图中:“输出:”前为输入的字符,后为输出的字符。
empty的初始值为10,full的初始值为0,输入字符最多可以连续读入10个,其他的在I/O缓冲区等待输入;当full的值大于0时,输出线程便可进行输出,每输出一个字符便会post一个empty信号量,此时输入线程接收到empty信号量便可开始从I/O缓冲区继续读取数据。根据实验结果可知:当输入数据很长或间断输入时,都可保证输出的和输入的字符和顺序完全一致,实验结果准确。
Task4:
实验要求:
进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码
(参见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发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?
程序代码:
/*
* Filename: Sender.c
* Description:
*/
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
/*
* Filename: Receiver.c
* Description:
*/
#include
#include
#include
#include
#include
#include
#include
#include
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;
}
代码执行:
打开两个终端,分别执行编译命令:
gcc -o Sender Sender.c -lpthread
gcc -o Receiver Receiver.c -lpthread
实验结果解析:
可见Sender进程发出的消息Receiver进程准确无误的接收;但Sender读入信息时用的是scanf("%s", shm_ptr)机制,遇到空格自动结束,则出现上述截图效果。
删除互斥的代码:
代码变化处仅以下两个地方:
Sender.c
Receiver.c
代码执行:
打开两个终端,分别执行编译命令:
gcc -o Sender2 Sender2.c -lpthread
gcc -o Receiver2 Receiver2.c -lpthread
实验结果:
实验结果解析:
可见Sender进程发出的消息Receiver进程接收出现错误;
当Sender进程未发送信息前,Receiver进程一直输出空信息;
当Sender进程发送信息后,Receiver进程一直输出刚接收到的信息;
因Sender读入信息时用的是scanf("%s", shm_ptr)机制,遇到空格自动结束,则读入“ I am YSS!”时,“YSS!”会覆盖前面的消息,Receiver进程没有接收到“ I am ”。
在发送和接收进程中打印输出共享内存地址:
代码变动:
只需分别在Sender、Receiver代码中添加:
printf(“共享内存地址:%p\n”,shm_ptr);
(具体代码请看Sender3.c、Receiver3.c)
原因分析:
共享内存映射函数shmat():
作用:将共享内存空间挂载到进程中
头文件:#include
函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg)
参数:
shmid : shmget()返回值
shmaddr: 共享内存的映射地址,一般为0(由系统自动分配地址)
shmflg : 访问权限和映射条件
在执行共享内存映射函数shm_ptr(shm_id, NULL, 0)时:参数shmaddr为NULL,则由系统自动分配地址,则两个进程共享内存地址可能不同。
实验测试:
b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
有名管道:
程序代码:
/*
*File: fifo_send.c
*/
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/my_fifo"
int main()
{
char buf[] = "hello,world";
//`. check the fifo file existed or not
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;
}
/*
*File: fifo_rcv.c
*/
#include
#include
#include
#include
#include
#include
#include
#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;
}
实验结果:
实验结果分析:
有名管道通信系统调用已经实现了同步机制。
阻塞情况见下表:
无名管道:
程序代码:
/*
* Filename: pipe.c
*/
#include
#include //for pipe()
#include //for memset()
#include //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;
}
实验结果分析:
无名管道通信系统调用已经实现了同步机制。
无名管道存储文件描述符:
阻塞情况见下表:
实验测试:
c)消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
程序代码:
client.c
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#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;
}
代码解析:
server.c:
等待接收客户端发送的数据,若时间超出600s,则自动exit;
当收到信息后,打印接收到的数据;并原样的发送给客户端,由客户端显示。
client.c:
启动两个进程(父子进程),父进程用于发送数据,子进程接收由server发送的数据;
发送数据:由使用者手动输入信息,回车后发送;当写入“end~”后,退出本进程。
接收数据:接收由Server端发送的数据信息,并打印。
实验结果:
实验结果分析:
消息通信系统调用已经实现了同步机制。
阻塞机制:当client不阻塞时,接受server消息的时候会一直打印空消息;当server不阻塞时,server会一直接受空消息并转发给client。
Task5:
实验要求:
阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。
内容较多,请点击以下链接:
http://www.cnblogs.com/laiy/p/pintos_project1_thread.html
https://wenku.baidu.com/view/9e94430671fe910ef02df884.html
如需了解更多知识,可点击以下链接:
https://github.com/yuanshaosui/OS/tree/master/实验三