预习

  1. Linux进程间通讯的主要方式有哪些?
    管道(有名管道和无名管道)、信号量、信号、消息队列、共享内存、套接字等。
  2. 用PV操作实现进程间的互斥与同步的步骤?
    1、确定进程间的关系
    2、设置信号量,说明含义、初值
    3、用伪代码描述
    在这里插入代码片
  3. 什么是管道?如何定义管道?如何读写管道?
    *管道是能够连接一个写进程和一个读进程,并允许它们以生产者-消费者方式进行通信的一个共享文件,又称pipe文件。
    定义管道:
pipe(fd);   
intfd[2];

访问管道:

read(fd[0],buf,size);

实验原理

1.进程的同步与互斥
进程的同步:所谓同步是指完成同一任务的进程间,由于需要在某些位置上协调它们的工作而相互等待,相互交换信息所产生的制约。
进程的互斥:进程之间因相互竞争(互斥、死锁、饥饿)使用独占型资源而产生的制约。

2.临界区问题(可以解决并发环境中不可再现性、不确定性——控制访问共享变量的代码)
一次仅允许一个进程使用的资源称为临界资源,而各进程必须互斥执行的那种程序段称为临界区。对临界区的调度原则为:不能假设各并发进程的相对执行速度,当没有进程在临界区时,允许一进程立即进入;若已经存在,其它需进入的进程必须等待,且进入临界区的必须在有限的时间内得到满足。

3.互斥的加锁实现与信号量和P、V操作
用锁操作原语实现互斥
信号量这一特殊变量,且其只能被P、V操作使用。P、V操作都是原语,所谓原语是指由若干条机器指令构成的一段程序,用以完成特定功能。原语在执行期间是不可分割的,执行中也不允许中断。
P、V操作定义如下:
P(S):
S:=S-l
若S≥0,则该进程继续执行;否则,该迸程进入S信号量的队列中等待,直到有其他进程在S上执行V操作为止。
V(S):
S:=S+l
若S>0,则进程继续执行;否则,释放S信号量队列上的一个等待进程,使之进入就绪队列,然后继续执行。
每执行一次P操作意味着要求分配一个资源,每执行一次V操作意味着要释放一个资源。

4.用P、V操作实现进程之间的互斥和同步
经典的同步互斥问题:读者、写者;生产者、消费者(有限缓冲、无限缓冲、环形缓冲);哲学家吃通心面等问题
实现互斥,令S初值为1,进程A、B竞争进入临界区的程序可以写成:
进程A 进程B
P(S); P(S);
临界区 临界区
V(S); V(S);
实现同步,设两个信号量并赋值为0,full表示缓冲区中装满信息;avail表示空信息。
进程A 进程B
P(full); P(avail);
把信息从缓冲区取走 把信息送人缓冲区
V(avail); V(full);

5.进程通信
进程之间的信息交换称为进程通讯:消息及共享存储区等等。

实验内容

  1. 消息的创建,发送和接收
    <任务>
    使用系统调用msgget( ), megsnd( ), msgrev( )及msgctl()编制一长度为1K的消息发送和接收的程序 。
    <程序设计>
    (1)为了便于操作和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和CLIENT,进行通信。
    (2)SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。
    (3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,即是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。
    (4)父进程在 SERVER和 CLIENT均退出后结束。
  2. 共享存储区的创建,附接和断接
    <任务>
    使用系统调用shmget(),sgmat(),smgdt(),shmctl()编制一个与上述功能相同的程序.
    <程序设计>
    (1)为了便于操作 和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和 CLIENT,进行通信。
    (2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1.作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER每接收到一次数据后显示”(server)received”.
    (3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送 请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT退出. CLIENT每发送一次数据后显示”(client)sent”.
    (4)父进程在SERVER和CLIENT均退出后结束.
    3.同步互斥
    (1)互斥
    互斥锁用来保证同一时间内只有一个线程在执行某段代码(临界区)。多线程编程最容易出问题的地方,就是临界区的界定和访问控制。下面是一个生产者,消费者的简单例子。生产者、消费者公用一个缓冲区,这里假定缓冲区只能存放一条消息。
    互斥
    (2)编写经典的“生产者-消费者”问题的实验,进一步熟悉Linux中的多线程编程,并且掌握用信号量处理线程间的同步和互斥问题。
    有一个有限缓冲区(这里用有名管道实现FIFO式缓冲区)和两个线程:生产者和消费者,它们不停地把产品放入缓冲区和从缓冲区拿走产品。一个生产者在缓冲区满的时候必须等待,一个消费者在缓冲区空的时候也必须等待。另外,因为缓冲区是临界资源,所以生产者和消费者之间必须互斥执行。它们之间的关系如下图所示
    生产者消费者问题
    这里要求使用有名管道来模拟有限缓冲区,并且使用信号量来解决“生产者—消费者”问题中的同步和互斥问题。
    信号量的考虑。这里使用3个信号量,其中两个信号量avail和full分别用于解决生产者和消费者之间的同步问题,mutex用于解决这两个线程之间的互斥问题。其中,avail表示有界缓冲区中的空单元数,初始值为N;full表示有界缓冲区中的非空单元数,初始值为0;mutex是互斥信号量,初始值为1。流程图如下:
    流程图
    本实验采用的有界缓冲区拥有3个单元,每个单元为5字节。为了尽量体现每个信号量的意义,在程序中生产过程和消费过程是随机(采取0~5s的随机时间间隔)进行的,而且生产者的速度比比消费者的速度平均快两倍左右(这种关系可以相反)。生产者一次生产一个单元的产品(放入“hello”字符串),消费者一次消费一个单元的产品。

实验步骤

  1. 消息的创建,发送和接收
    <任务>
    使用系统调用msgget( ), megsnd( ), msgrev( )及msgctl()编制一长度为1K的消息发送和接收的程序 。
    <程序设计>
    (1)为了便于操作和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和CLIENT,进行通信。
    (2)SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。
    (3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,即是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。
    (4)父进程在 SERVER和 CLIENT均退出后结束。
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <string.h>


#define MSG_SIZE 1024

struct msgt
{
    long msgtype;
    char msgtext[MSG_SIZE];
};


int main()
{
    pid_t pid_A;
    pid_t pid_B;   /*父进程中创建两个子进程*/
     
    key_t key;
    int msgid;
    int msg_type;
    char str[256];
    struct msgt send_msg;
    struct msgt rcv_msg;
    char i = 10;
    
    /*1.新建键值*/
    key = ftok("/home", 4);     

    /*2.创建消息队列*/    
    msgid = msgget(key, IPC_CREAT);    
    
    /*2.创建子进程A*/    
    pid_A = fork();
    
    if( pid_A > 0 )
    {
        /*父进程操作*/
        pid_B = fork();
        if( pid_B > 0 )
        {
            /*父进程操作*/
     
            /*等待A进程退出*/
            wait(NULL);
    
            /*等待B进程退出*/
            wait(NULL);        
                    
        }else if( pid_B == 0 ){
            /*子进程B操作,CLIENT*/   
             
            for( i=10; i > 0; i-- )
            {
                printf("(client)sent! msg_type is:%d\n",i);
                /*填充消息数据*/
                send_msg.msgtype = i;
                strcpy( send_msg.msgtext, "(server)received!" );          	
            	sleep(1);
                /*发送消息*/
                msgsnd( msgid, &send_msg, sizeof(struct msgt), 0);
            }            

            exit(0);         
        }
    }else if( pid_A == 0 ){
        /*子进程A操作,SERVER*/ 
        
        while(1){
            /*接收消息队列*/
            msgrcv( msgid, &rcv_msg, sizeof(struct msgt), 0, 0);

            /*打印消息队列数据*/
            printf("Message text:%s,msg_type is:%d\n", rcv_msg.msgtext ,rcv_msg.msgtype);            	
            
            if( rcv_msg.msgtype == 1 ){
                printf("rcv_msgtype is 1.\n");
            	break;
            }
        }
              
        exit(0);  
    }   
    printf("over!\n");
    /*删除消息队列*/
    msgctl( msgid, IPC_RMID, 0);        
    exit(0);
    
}
  1. 共享存储区的创建,附接和断接
    <任务>
    使用系统调用shmget(),sgmat(),smgdt(),shmctl()编制一个与上述功能相同的程序.
    <程序设计>
    (1)为了便于操作 和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和 CLIENT,进行通信。
    (2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1.作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER每接收到一次数据后显示”(server)received”.
    (3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送 请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT退出. CLIENT每发送一次数据后显示”(client)sent”.
    (4)父进程在SERVER和CLIENT均退出后结束.
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/shm.h>
#include <stdlib.h>	/*EXIT_FAILURE*/


#define TEXT_SIZE 1024

struct shared_use_st
{
    int written_by_you;
    char some_text[TEXT_SIZE];
};

int main()
{
    pid_t pid_A;
    pid_t pid_B;   

    key_t key;
    int shmid;
    struct shared_use_st *shared_server_stuff;
    struct shared_use_st *shared_client_stuff;
    int running = 1; /*1时,循环正常运行*/
    char i;

    key = ftok("/home",1);
    /*创建/获取共享内存*/
    shmid = shmget(key, sizeof(struct shared_use_st), IPC_CREAT);
    if( shmid == -1)
    {
        printf("Creat share memory fail!\n");
        exit(EXIT_FAILURE);	/*EXIT_FAILURE*/
    }     
    
    pid_A = fork();
    
    if( pid_A > 0 ){
    	
        /*父进程操作*/
        pid_B = fork();
        if( pid_B > 0 )
        {
            /*父进程操作*/
     
            /*等待A进程退出*/
            wait(NULL);
    
            /*等待B进程退出*/
            wait(NULL);        
                    
        }else if( pid_B == 0 ){
            /*子进程B操作,CLIENT*/   
            //1.映射共享内存*/
            shared_client_stuff = (struct shared_use_st *)shmat(shmid, NULL, 0);
            
            for( i=10; i >= 0; i-- )
            {

	        while( shared_client_stuff->written_by_you != -1 )
	        {
	            sleep(1);
	            printf("wait read process!\n");
	        }

                //3.2将用户输入的字符串放入共享内存/
                strncpy(shared_client_stuff->some_text, "(server)received!", TEXT_SIZE);        
                shared_client_stuff->written_by_you = i;
                printf("(client)sent!\n");
             }   
      
            //分离共享内存*/
            shmdt((const void *)shared_client_stuff);  

            exit(0);         
        }
    }else if( pid_A == 0 ){
        /*子进程A操作,SERVER*/        
        /*映射共享内存*/
        shared_server_stuff = (struct shared_use_st *)shmat(shmid, NULL, 0);
    
        shared_server_stuff->written_by_you = -1;  /*先运行read,所以首先初始值给0*/
    
        /*循环*/
        while( running )
        {
          if( (shared_server_stuff->written_by_you != 0) && (shared_server_stuff->written_by_you != -1))
            {
            	
                printf("Server rec: %s\n", shared_server_stuff->some_text);
                shared_server_stuff->written_by_you = -1;
                         
            }else if(shared_server_stuff->written_by_you == 0){

                printf("Server rec: %d,ending...\n",shared_server_stuff->written_by_you);
                running = 0;  
            
            }
            sleep(0.1);
        }
        /*分离共享内存*/
        shmdt((const void *)shared_server_stuff);    
  
        exit(0);  
    }
    
    printf("over!\n");
    /*5.删除共享内存*/  
    shmctl(shmid, IPC_RMID, 0);     
  
    exit(0);    
}

  1. 用管道实现进程间的消息通讯
    管道在逻辑上被看作管道文件,在物理上则由文件系统的高速缓冲区构成,而很少启动外设。发送进程利用文件系统的系统调用write(fd[1],buf,size),把buf中的长度为size字符的消息送入管道入口fd[1],接收进程则使用系统调用read(fd[0],buf,size)从管道出口fd[0] 读出size字符的消息置入buf 中。这里,管道按FIFO(先进先出)方式传送消息,且只能单向传送消息(图)。
    利用UNIX提供的系统调用pipe,可建立一条同步通信管道。其格式为:
    pipe(fd)
    int fd[2];
    这里,fd[1] 为写入端,fd[0]为读出端。
    在这里插入图片描述
    1)、简单管道通讯问题
    用C语言编写一个程序,建立一个pipe,同时父进程生成一个子进程,子进程向pipe中写入一字符串,父进程从pipe中读出该字符串。
#include(stdio.h)
main()
	{
		int x,fd[2];
		char buf[30],s[30];
		pipe(fd);/*创建管道*/
		x=fork();
		//while((x=fork())=-1);/*创建子进程失败时,循环*/
		if(x>0)//父进程
	{
		printf("This is father process\n");
		read(fd[0],s,30);//父进程读取管道中字符
		wait(NULL);
		printf("%s\n",s);
		close(fd[0]);
	}
	else
	{
		printf("Now in child process\n");
		sprintf(buf,"This is an exanplen");
		write(fd[1],buf,30);//把buf中字符写入管道
		close(fd[1]);
		exit(0);
	}
	exit(0)

2)、实现多进程的管理通信。
使用系统调用pipe()建立一条管道线;两个子进程P1和P2分别向管道中写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。
要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。如何让两个子进程P1和P2 互斥使用管道?(提示,调用lockf()来给管道进行加锁,可以实现进程之间的互斥)

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>


int main()
{
    pid_t pid_A;
    pid_t pid_B;    
    int pipefd[2];
    char a_buffer[10];
    char b_buffer[10];       
    
    /*1.创建管道*/
    pipe(pipefd);
    
    /*2.创建子进程A*/    
    pid_A = fork();
    
    if( pid_A > 0 )
    {
        /*父进程操作*/
        pid_B = fork();
        if( pid_B > 0 )
        {
            /*父进程操作*/
            printf("This is father process\n");
            /*父进程读A数据*/
            read(pipefd[0], a_buffer, 8);       
            /*等待A进程退出*/
            wait(NULL);
            printf("father read:%s\n",a_buffer);
                        
            /*父进程读B数据*/
            read(pipefd[0], b_buffer, 8);       
            /*等待B进程退出*/
            wait(NULL);        
                    

            printf("father read:%s\n",b_buffer);
            
            close(pipefd[0]);
                    
        }else{
            /*子进程B操作*/   
                     
            /*锁定写入端*/
            lockf(pipefd[1],1,0);
            /*写入数据*/
            write(pipefd[1], "Hello_B",8);
            sleep(5);
            lockf(pipefd[1],0,0); 
            exit(0);         
        }
    }else{
        /*锁定写入端*/
        lockf(pipefd[1],1,0);
        /*写入数据*/
        write(pipefd[1], "Hello_A",8);
        sleep(5);
        lockf(pipefd[1],0,0);  

        close(pipefd[1]);              
        exit(0);  
    }
        
    exit(0);
}
Logo

更多推荐