16281053_杨瑷彤_操作系统第三次实验

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不执行。

扫描二维码关注公众号,回复: 6533304 查看本文章

在这里插入图片描述
由此,经过测试可以验证正确性。

第二题

火车票余票数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);
}

这个函数的功能是保存当前线程的状态,并恢复已保存的线程状态。

猜你喜欢

转载自blog.csdn.net/weixin_42718893/article/details/89090924