目录

进程间通信的目的

进程间通信的几种方式

无名管道

命名管道

创建键值函数ftok()

消息队列

创建或引用一个消息队列msgget()

向消息队列中添加消息msgsnd()

从消息队列中读取消息msgrcv()

控制消息队列msgctl()

共享内存

创建或引用一个共享内存shmget()

将共享内存链接到当前进程的地址空间shmat()

断开共享内存与当前进程的地址空间的链接shmdt()

控制共享内存shmc

管道、消息队列、共享内存的区别

信号

向某个进程发送信号函数kill()

信号接收函数signal()

高级信号发送函数sigqueue(),比kill()在发射信号时能携带更多信息

高级信号接收函数sigaction(),比signal()在接收信号时能获取更多信息

信号量

 创建一个信号量集semget()

控制信号量集或获取信号量的信息semctl()

信号量控制共享资源的操作,即PV操作semop()

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它发生了某种事件。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程间通信的几种方式

无名管道

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

int main()
{
    pid_t pid;
    int fd[2];
    char readBuf[128];

    if(pipe(fd) < 0){
        perror("pipe");
        exit(-1);
    }

    pid = vfork();

    if(pid == 0){
        printf("this is child process\n");
        close(fd[0]);
        write(fd[1],"my name is child process,haozige handsome",128);
        printf("child process is done\n");
        exit(0);
    
    }else if(pid > 0){
        printf("this is father process\n");
        wait(NULL);
        close(fd[1]);
        read(fd[0],readBuf,128);
        printf("from child process content is %s\n",readBuf);
        printf("father process is done\n");

    }else{
        printf("fork is fail\n");
        perror("fork");
        exit(-1);
    }

    return 0;
}

命名管道

与无名管道不同的是,命名管道可以在没有关系的两个进程间进行通信

由于read()函数在读不到数据时会阻塞,因此我们在读数据的进程创建命名管道,并阻塞等待写数据的进程向命名管道写入数据 ,在创建命名管道时,给管道的权限建议是0600,即可读可写可执行

读数据进程

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc,char *argv[])
{
    if(argc < 2){
        printf("missing paramiter -> FIFO");
        exit(-1);
    }

    char readBuf[128];
    int readSize = 0;

    if(mkfifo(argv[1],0600) < 0){
        perror("mkfifo");
        exit(-1);
    }

    int fd = open(argv[1],O_RDONLY);
    if(fd < 0){
        perror("open");
        exit(-1);
    }

    while(1){
        memset(readBuf,'\0',128);
        readSize = read(fd,readBuf,128);
        printf("read data from FIFO %s, content is %s\n",argv[1],readBuf);
    }
    close(fd);

    return 0;
}

命名管道是类似创建文件的形式创建的  

写数据进程

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc,char *argv[])
{
    if(argc < 2){
        printf("missing paramiter -> FIFO");
        exit(-1);
    }

    char Msg[128] = "from write FIFO send jiangxiaoya is pigHead";

    int fd = open(argv[1],O_WRONLY);

    while(1){
        write(fd,Msg,strlen(Msg));
        sleep(1);
    }
    close(fd);

    return 0;
}

 当写数据进程启动后,读数据进程不再阻塞,开始读取命名管道的数据

命名管道的应用:

  • shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件。
  • 客户进程-服务器进程应用程中,FIFO用作汇聚点,在客户进程和服务器进程二者之间传递数据。

创建键值函数ftok()

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main(int argc,char *argv[])
{
    if(argc < 2){
        printf("missing parameter -> keyfile\n");
        exit(-1);
    }
    key_t key;
    key = ftok(argv[1],'z');

    if(key == -1){
        perror("ftok");
        exit(-1);
    }else{
        printf("key value is %d\n",key);
    }

    key = ftok(argv[1],159);

    if(key == -1){
        perror("ftok");
        exit(-1);
    }else{
        printf("key value is %d\n",key);
    }

    return 0;
}

消息队列

消息队列是一个存储信息的链表,全双工通信,进程终止后,数据不会丢式 

创建或引用一个消息队列msgget()

向消息队列中添加消息msgsnd()

从消息队列中读取消息msgrcv()

控制消息队列msgctl()

 

 向消息队列添加各种消息的sendMsg进程

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct Msgbuf
{
    long mtype;
    char mtext[128];
};

int main(int argc,char *argv[])
{
    key_t key;
    key = ftok("./keyfile.txt",'z');

    if(key == -1){
        perror("ftok");
        exit(-1);
    }

    int msgID = msgget(key,IPC_CREAT|0777);

    if(msgID == -1){
        perror("msgget");
        exit(-1);
    }

    struct Msgbuf sendMsg = {
        130,
        "this is sendMsg process"
    };

    struct Msgbuf sendMsg1 = {
        181,
        "haozige handsome"
    };

    struct Msgbuf sendMsg2 = {
        166,
        "jiangxiaoya pigHand"
    };

	msgsnd(msgID,&sendMsg,strlen(sendMsg.mtext),0);
    msgsnd(msgID,&sendMsg1,strlen(sendMsg1.mtext),0);
    msgsnd(msgID,&sendMsg2,strlen(sendMsg2.mtext),0);

    struct Msgbuf receive;
    memset(&receive,0,sizeof(receive));
    msgrcv(msgID,&receive,sizeof(receive.mtext),2349,0);

    printf("this is sendMsg process,receive msg from messageQueue,content is %s\n",receive.mtext);

    //msgctl(msgID,IPC_RMID,NULL);

    return 0;
}

根据消息字段读取消息队列中的消息的receive进程,要使用同一个键值才能访问到同一个消息队列 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <unistd.h>

struct Msgbuf
{
    long mtype;
    char mtext[128];
};

int main(int argc,char *argv[])
{
    key_t key;
    key = ftok("./keyfile.txt",'z');

    if(key == -1){
        perror("ftok");
        exit(-1);
    }

    int msgID = msgget(key,IPC_CREAT|0777);

    if(msgID == -1){
        perror("msgget");
        exit(-1);
    }

    struct Msgbuf sendMsg = {
        2349,
        "this is receiveMsg process"
    };

    msgsnd(msgID,&sendMsg,strlen(sendMsg.mtext),0);

    struct Msgbuf receive;
    struct Msgbuf receive1;
    struct Msgbuf receive2;
	memset(&receive,0,sizeof(receive));
    memset(&receive1,0,sizeof(receive1));
    memset(&receive2,0,sizeof(receive2));

    msgrcv(msgID,&receive,sizeof(receive.mtext),130,0);
    msgrcv(msgID,&receive1,sizeof(receive1.mtext),181,0);
    msgrcv(msgID,&receive2,sizeof(receive2.mtext),166,0);

    printf("this is receiveMsg process,receive msg from messageQueue,content is %s\n",receive.mtext);
    sleep(5);
    printf("this is receiveMsg process,receive msg from messageQueue,content is %s\n",receive1.mtext);
    sleep(5);
    printf("this is receiveMsg process,receive msg from messageQueue,content is %s\n",receive2.mtext);

    msgctl(msgID,IPC_RMID,NULL);

    return 0;
}

 

共享内存

创建或引用一个共享内存shmget()

将共享内存链接到当前进程的地址空间shmat()

断开共享内存与当前进程的地址空间的链接shmdt()

控制共享内存shmctl

 创建一块共享内存,writeShm进程每隔1秒向共享内存写入一个字母,readShm进程每隔一秒从共享内存读取一个字母,当从共享内存连续10s读不到数据后,会删除该共享内存

my_writeShm进程:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>


int main(int argc,char *argv[])
{
    key_t key = ftok(".",'z');

    if(key == -1){
        perror("ftok");
        exit(-1);
    }

    int shmID = shmget(key,1024*4,IPC_CREAT|0666);

    if(shmID == -1){
        perror("shmget");
        exit(-1);
    }

    char *tmp = (char *)shmat(shmID,NULL,0);
    memset(tmp,'\0',1024*4);
    char *str = "abcdefghijklmnopqrstuvwxyz";

    while(*str != '\0'){
        sprintf(tmp,"%c",*str);
        sleep(1);
        *str++;
    }
    memset(tmp,'\0',1024*4);

    shmdt(tmp);

    //shmctl(shmID,IPC_RMID,NULL);

    return 0;
}

my_readShm进程: 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>


int main(int argc,char *argv[])
{
    key_t key = ftok(".",'z');

    if(key == -1){
        perror("ftok");
        exit(-1);
    }

    int shmID = shmget(key,1024*4,IPC_CREAT|0666);

    if(shmID == -1){
        perror("shmget");
        exit(-1);
    }

    char *tmp = (char *)shmat(shmID,NULL,0);
    while(1){
        printf("shm content is %s\n",tmp);
        sleep(1);

        if(strlen(tmp) == 0){
            sleep(10);
            if(strlen(tmp) == 0){
                break;
            }
        }
    }
    shmdt(tmp);

    shmctl(shmID,IPC_RMID,NULL);

    return 0;
}

管道、消息队列、共享内存的区别

将三者进程间的通信看作纸条上的通信

  • 管道:一方拿走这个纸条进行写内容,另一方只能等对方写完这个纸条才能拿走这个纸条进行读内容,半双工通信
  • 消息队列:在一个纸箱存放着多张纸条,纸条上有号码标注,每一张纸条都是独一无二,一方拿走了一张纸条进行写内容或读内容,同时另一方也拿走了一张纸条进行写内容或读内容,操作完后会把纸条放回纸箱中,全双工通信
  • 共享内存:双方同时拿着纸条,一方在纸条写内容,另一方实时在纸条上读内容(实时性)

信号

向某个进程发送信号函数kill()

信号接收函数signal()

使用signal()函数实现Linux定时器,每隔2s输出一个字符串,使用kill()关闭这个定时器

signal()进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

static int i = 0;

void time_handle(int signum)
{
    i++;
    if(i == 2000){
        printf("2s to hello haozige,my process pid is %d\n",getpid());
        i = 0;
    }
}

int main()
{

    struct itimerval tmp;

    tmp.it_value.tv_sec = 0;
    tmp.it_value.tv_usec = 500*1000;

    tmp.it_interval.tv_sec = 0;
    tmp.it_interval.tv_usec = 500;

    if(setitimer(ITIMER_REAL,&tmp,NULL) == -1){
        perror("setitimer");
        exit(-1);
    }//SIGALRM

    signal(SIGALRM,time_handle);
    while(1);
    return 0;
}

相关结构体

 

 

 kill()进程

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


int main(int argc,char *argv[])
{
    if(argc < 2){
        printf("missing parameter -> process pid\n");
        exit(-1);
    }

    pid_t pid = atoi(argv[1]);

    kill(pid,SIGINT);
    sleep(1);
    printf("pid is %d process is done!\n",pid);
    return 0;
}

高级信号发送函数sigqueue(),比kill()在发射信号时能携带更多信息

 

高级信号接收函数sigaction(),比signal()在接收信号时能获取更多信息

sigset_t sa_mask:

 int sa_flags:

 siginfo_t 结构:保存的其他信息

进程A使用sigqueue向进程B发送信号和携带信息:年龄21和年龄20两个整型数,进程B收到该信号后进行处理

进程A:sigqueue()

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

int main(int argc,char *argv[])
{
    if(argc < 2){
        printf("missing parameter -> process pid\n");
        exit(-1);
    }

    pid_t pid = atoi(argv[1]);

    int tmp = 0;
    union sigval msg;
    msg.sival_int = 21;

    while(1){
        sigqueue(pid,SIGUSR1,msg);
        tmp++;
        if(tmp == 5){
            msg.sival_int = 19;
        }
        if(tmp == 10){
            kill(pid,SIGINT);
            break;
        }
        sleep(1);
    }
    return 0;
}

进程B:sigaction() 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

static int i = 0;

void time_handle(int signum,siginfo_t *tmp,void *context)
{

    if(context != NULL){
        printf("msg from pid %d process\n",tmp->si_pid);
        printf("age is %d\n",tmp->si_value.sival_int);
        printf("age is %d\n",tmp->si_int);
    }else{
        printf("Segment errors may occur\n");
        printf("1%s",(char *)context);
    }
}

int main()
{
    struct sigaction sigMsg;
    printf("my process pid is %d\n",getpid());

    sigMsg.sa_flags = SA_SIGINFO;
    sigMsg.sa_sigaction = time_handle;

    sigemptyset(&sigMsg.sa_mask);//初始化信号集,清空该信号集里的所有信号

    sigaction(SIGUSR1,&sigMsg,NULL);

    while(1);
    return 0;
}

信号量

 信号量并非单个非负值,必须定义为多个信号量的集合,称为信号量集

 创建一个信号量集semget()

控制信号量集或获取信号量的信息semctl()

 

 

信号量控制共享资源的操作,即PV操作semop()

 

 信号量保证子进程先运行,父进程后运行,同理子进程先获得临界资源,父进程后获得临界资源

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>

//semctl()参数4,保存信号量的信息或者设置信号量的信息
union semun {
    int val;    
    struct semid_ds *buf;    
    unsigned short  *array;  
    struct seminfo  *__buf;  
};

//p操作
void handle_P(int id)
{
    struct sembuf pBuf;
    pBuf.sem_num = 0;
    pBuf.sem_op = -1;
    pBuf.sem_flg = SEM_UNDO;
    if(semop(id,&pBuf,1) < 0){
        perror("semop");
        exit(-1);
    }
}

//v操作
void handle_V(int id)
{
    struct sembuf vBuf;
    vBuf.sem_num = 0;
    vBuf.sem_op = 1;
    vBuf.sem_flg = SEM_UNDO;
    if(semop(id,&vBuf,1) < 0){
        perror("semop");
        exit(-1);
    }
}

int main(int argc,char *argv[])
{
    key_t key = ftok(".",'z');

    if(key == -1){
        perror("ftok");
        exit(-1);
    }

    int semID = semget(key,1,IPC_CREAT|0666);

    if(semID < 0){
        perror("semget");
        exit(-1);
    }

    union semun tmp;
    tmp.val = 0; //信号量初始值

    if(semctl(semID,0,SETVAL,tmp) < 0){
        perror("semctl");
        exit(-1);
    }
    pid_t pid = fork();
    if(pid > 0){
		//如果父进程先运行,由于信号量初始值为0,执行p操作后父进程进入阻塞状态
        printf("father process is wait\n");
        handle_P(semID);//p操作
        printf("child process lets father process cancel wait\n");

        semctl(semID,0,IPC_RMID);

    }else if(pid == 0){
		//子进程运行
        printf("child process function\n");
		//5s后执行v操作唤醒父进程
        printf("wake up the father process after 5s\n");
        int i;
        for(i=1;i<6;i++){
            sleep(1);
            printf("%d\n",i);
        }
        handle_V(semID);//v操作
   
   }else{
        perror("fork");
        exit(-1);
    }

    return 0;
}

Logo

更多推荐