Linux系统编程——进程间通信

目录

一、无名管道

相关概念

相关函数介

相关说明

实战

 二、有名管道

有名管道和无名管道的区别

有名管道和无名管道的相同点

有名管道的相关函数

实战

三、消息队列

消息队列的介绍

消息队列的特点      

相关函数

实战

四、共享内存

共享内存优缺点

共享内存的特点

相关函数介绍

实战

五、信号

信号概述

信号入门版——不带信息,单纯地接受和发送信号

信号高级版——带消息

实战

六、信号量

信号量概述

信号量简述

相关函数介绍

七、ipcs指令与ipcrm指令


一、无名管道

  • 相关概念

  1. 无名管道和有名管道是Linux系统内核的特殊文件,用于进程之间的通信。
  2. 无名管道是在父进程与子进程之间通信使用的,并且只能用于父子进程间的通信。
  3. 无名管道相当于一个队列结构。其中信息读出后即删除,再次读取时即为下一个信息。 
  • 相关函数介

    • pipe函数:int pipe(fd[2]),fd[1]为写入端(入队),fd[0]为读出端(出队)        

      1. 该函数的作用为创造无名管道文件。由于是特殊文件所以不可由open函数创建。
      2. 无名管道是Linux系统内核的特殊文件,所以fd[0]和fd[1]都是文件描述符。
      3. 成功返回0,失败返回-1。
  • 相关说明

  1. 管道是创建在内存中的,当进程结束时,空间就会释放。
  2. 无名管道和有名管道都是半双工通信,若想实现同时的双向通信需要建立两个管道。
  3. 无名管道是Linux特殊文件,不能再Windows共享文件夹中创建。
  4. 无名管道只能在父子进程间通信。
  • 实战

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
        pid_t pid;
        int fd[2];
        char buf[1024] = {0};

        if(pipe(fd) < 0){
                printf("creat pipe failed!\n");
        }

        pid = fork();
        if(pid < 0){
                perror("fork");
                exit(1);
        }

        if(pid == 0){
                printf("this is child!\n");
                close(fd[1]);
                int nread = read(fd[0],buf,1024);
                printf("read:%d,content:%s\n",nread,buf);
                exit(0);
        }else{
                printf("this is fsther!\n");
                close(fd[0]);
                write(fd[1],"father's data!",20);
                wait(NULL);
        }

        return 0;
}

程序运行后结果为 

 二、有名管道

  • 有名管道和无名管道的区别

  1. 无名管道只能用于父子进程之间通信,而有名管道可用于非父子进程之间的通信。
  2. 无名管道通过pipe函数创建出来的不需要通过open函数打开,而有名函数创建出来的需要通过open函数打开。并且有名管道在创建管道时需要设置权限,而无名管道不用。
  3. 无名管道会随着进程的结束而消失,而有名管道不会,但是有名管道中的数据会随着数据的读出而消除。
  4. 无名管道的文件无法在系统中看到,而有名管道FIFO文件可以在系统中看到。所以有名管道比无名管道更持久跟稳定。
  • 有名管道和无名管道的相同点

  1. 有名管道和无名管道都属于系统的特殊文件。
  2. 有名管道和无名管道都属于半双工通信。
  3. 无名管道和有名管道都是通过write函数和read函数进行读写的。
  • 有名管道的相关函数

    • int mkfifo(const char *pathname, mode_t mode);
    1. 该函数作用是创建有名管道的FIFO文件
    2. *pathname为路径名,mode为权限。
    3. 成功返回0,失败返回-1。
  • 实战

当读程序与写程序都进行了管道创建,则要注意去除EEXIST错误信息。

注意:当使用open函数打开时,尽量使用只读或只写的权限打开

读与创建程序

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

int main()
{
        int fd;
        char buf[128] = {0};

        if((mkfifo("./file",0777) < 0) & (errno != EEXIST)){
                printf("create fifo failed!\n");
        }

        fd = open("./file",O_RDONLY);

        while(1){
                int nread = read(fd,buf,128);

                printf("read:%d,context:%s\n",nread,buf);
        }
        close(fd);

        return 0;
}

 写与创建程序

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

int main()
{
        int fd;
        int cnt = 0;
        char buf[128] = "zhen shuai!";

        if((mkfifo("./file",0777) < 0) & (errno != EEXIST)){
                printf("create fifo failed!\n");
        }

        fd = open("./file",O_WRONLY);

        while(1){
                write(fd,buf,strlen(buf));
                sleep(1);
                if(cnt == 5){
                        break;
                }
                cnt++;
        }

        close(fd);

        return 0;
}

三、消息队列

  • 消息队列的介绍

    • 消息队列通过名字字面意思理解就是队列排队结构,消息队列与FIFO很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,多个进程从队列中读取信息。但FIFO需要读、写的两端事先都打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。          
  • 消息队列的特点      

    1. 消息队列属于顺序队列形式的结构,向队列里写的每一条消息,会追加到队列后面,读取一个就从队列里消除一个。
    2. 写函数不会阻塞,除非队列里存放的消息数量已经满了,才会导致阻塞。
    3. 读函数,从队列里读取不到数据时,会阻塞。
    4. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

    5. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

  • 相关函数

    函数原型函数作用及返回值函数参数

    int msgget(key_t key, int flag);

    创建或打开消息队列:成功返回队列ID,失败返回-1key:消息对列的表示(一般通过ftok函数获得)
    flag:指名队列的访问权限和创建标志,创建标志为IPC_CREAT、IPC_EXC

    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

    添加消息:成功返回0,失败返回-1

    msgid:消息队列的id,通常为msgget的返回值

    msgp:是消息结构体。long mtype:接受进程可以用此值进行消息选择
    char mtext:是一个数组,他的最大允许长度由msgsz决定
    msgsz:用来决定mtext中最大长度。
    msgflg:0是阻塞、IPC_NOWAIT队列满时函数不等待直接返回、IPC_NOERROR当发送字节大于size字节时,阶段部分将被丢弃,且不通知发送进程。

    int msgrcv(int msqid, const void *msgp, size_t msgsz, long msgtyp,int msgflg);

    读取消息:成功返回消息数据的长度,失败返回-1msgid:与msgsnd函数一样。
    msgp:与msgsnd函数一样。
    msgsz:与msgsnd函数一样。

    msgtyp>0:接受第一个类型等于msgtyp的消息

    msgtyp==0:接受队列中的第一个消息

    msgtyp<0:接受类型小于或等于msgtyp绝对值的第一个最低类型消息

    msgflg:与msgsnd函数一样。
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);控制消息队列,常用来删除消息队列:成功返回0,失败返回-1msqid:消息队列标识符
    cmd:通常为IPC_RMID表示删除消息队列
    buf:通常为NULL
  • 实战

        发送信息

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

struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[128];    /* message data */
};

int main()
{
        key_t key = ftok(".",1);
        int msqid;
        struct msgbuf msgp = {888,"xiaoyang zhen shuai!"};

        msqid = msgget(key,IPC_CREAT|0777);

        int nrcv = msgsnd(msqid,&msgp,strlen(msgp.mtext),0);

        msgctl(msqid,IPC_RMID,0);

        return 0;
}

        接受信息

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

struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[128];    /* message data */
};

int main()
{
        key_t key = ftok(".",1);;
        int msqid;
        struct msgbuf msgp;

        msqid = msgget(key,IPC_CREAT|0777);

        if(msqid < 0){
                printf("creat failed!\n");
                perror("why");
        }

        int nrcv = msgrcv(msqid,&msgp,128,888,0);

        printf("rcv = %d,context = %s\n",nrcv,msgp.mtext);

        msgctl(msqid,IPC_RMID,0);

        return 0;
}
     

四、共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

  • 共享内存优缺点

    • 共享内存相比其他几种方式有着更方便的数据控制能力,数据在读写过程中会更透明。当成功导入一块共享内存后,它只是相当于一个字符串指针来指向一块内存,在当前进程下用户可以随意的访问。缺点是,数据写入进程或数据读出进程中,需要附加的数据结构控制。
  • 共享内存的特点

    1. 由于是直接对内存进行读取,所以共享内存是最快的一种IPC
    2. 因为可以多个进程同时操作,所以需要进行同步
    3. 共享内存通常和信号量共同使用,信号量用来同步共享内存的访问
  • 相关函数介绍

函数名称函数作用及返回值函数参数
int shmget(key_t key,size_t size,int flag)用来创建共享内存。成功返回共享内存ID,失败返回-1。key:用来变换成一个标识符
size:为要请求的内存长度(内核是以页为单位分配内存,当size参数的值不是系统内存页长的整数倍时,系统会分配给进程最小的可以满足size长的页数,但是最后一页的剩余部分内存是不可用的。)
flag:IPC_CREAT(注意要增加权限)、IPC_EXCL
void *shmat(int shm_id, const void *addr, int flag);用来建立进程与共享内存之间的连接。成功返回指向共享内存的指针,失败返回-1。shm_id:指定要引入的共享内存
addr:和flag共同决定引入的地址值addr为0,表明让内核来决定第1个可以引入的位置。
flag:和addr共同决定引入的地址值addr非零,并且flag中指定SHM_RND,则此段引入到addr所指向的位置(此操作不推荐使用,因为不会只对一种硬件上运行应用程序,为了程序的通用性推荐使用第1种方法)。为0则为读写的方式,为SHM_RDONLY为只读。
int shmdt(void *addr);用来断开进程与共享内存之间的连接。成功返回0,失败返回-1。参数addr是调用shmat函数的返回值
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);用来根据cmd指令对共享内存进行操作,刚用来删除共享内存。成功返回0,失败返回-1。shm_id:指定要控制的共享内存
cmd:常用的是IPC_RMID(从系统中删除该共享内存)。
buf:通常再删除时为0.
  • 实战

    • 读数据
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>

int main()
{
        key_t key;
        int shmid;
        char *shmaddr;

        key = ftok(".",1);

//      shmid = shmget(key,1024 * 4,IPC_CREAT|0666);
        shmid = shmget(key,1024 * 4,0);
        if(shmaddr < 0){
                printf("shmget failed!\n");
        }

        shmaddr = shmat(shmid,NULL,0);

        printf("data = %s\n",shmaddr);

        shmdt(shmaddr);
        shmctl(shmid,IPC_RMID,0);

        return 0;
}

写数据

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

int main()
{
        int shmid;
        key_t key;
        char *shmaddr;
        char buf[128] = {0};

        key = ftok(".",1);

        shmid = shmget(key,1024 * 4,IPC_CREAT|0666);
        if(shmid > 0){
                printf("shmget success!\n");
        }

        shmaddr = shmat(shmid,NULL,0);

        strcpy(shmaddr,"xiaoyang");
        printf("write success!\n");

        shmdt(shmaddr);

        return 0;
}

五、信号

  • 信号概述

    1. 对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
    2. 信号的名字和编号:每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。信号定义在signal.h头文件中,信号名都定义为正整数。具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用

    3. 信号的处理:信号的处理有三种方法,分别是:忽略、捕捉和默认动作

      1. 忽略信号:为忽略信号不执行原本的操作,但是sigkill和sigstop无法忽略。可以通过SIG_IGN这个宏进行忽略

      2. 捕捉信号:通过用户自定义函数来告诉内核,当信号到来时调用该函数,做相应的操作操作。

      3. 默认动作:系统默认的动作,每个信号都有其对应的默认动作。

    4. 当执行一个程序时,所有信号的状态都是系统默认或者忽略状态的。除非是调用exec进程忽略了某些信号。exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不会改变 。
    5. 当一个进程调动了 fork 函数,那么子进程会继承父进程的信号处理方式。
  • 信号入门版——不带信息,单纯地接受和发送信号

    1. kill指令发送信号给进程,如发送序号为9的SIGKILL信号,kill -9 pid
    2. 相关函数介绍
      函数名称函数作用及返回值函数参数
      int kill(pid_t pid, int sig);向对应的pid进程发送信号。成功返回0,失败返回-1.pid:对应进程的pid
      sig:需要发送的信号的序号
      sighandler_t signal(int signum, sighandler_t handler);信号注册函数。成功返回一个特定处理函数的指针,失败返回SIG_ERRsignum:信号的序号
      handle:函数指针,指向中断函数
    3. 实战

#include <stdio.h>
#include <signal.h>

//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);

void sighandle(int signum)
{
        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 1:
                        printf("SIGUP\n");
                        break;
                case 10:
                        printf("SIGUSE1\n");
                        break;

        }

}

int main()
{
        signal(SIGINT,SIG_IGN);
        signal(SIGUSR1,sighandle);
        signal(SIGHUP,sighandle);

        while(1);
        return 0;
}

        

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

//int kill(pid_t pid, int sig);

int main(int argc,char **argv)
{
        int pid;
        int signum;
        char cmd[128] = {0};

        if(argc != 3){
                printf("input error!\n");
        }

        pid = atoi(argv[2]);
        signum = atoi(argv[1]);

        sprintf(cmd,"kill -%d -%d",signum,pid);

//      if((kill(pid,signum)) < 0){
//              perror("kill");
//      }

        system(cmd);

        return 0;
}
  • 信号高级版——带消息

    1. 相关函数介绍
    2. 在使用sigqueue函数时需要确认的几点操作
      1. 使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
      2. sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据
函数名称函数作用以及返回值函数参数
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);接受信号及其携带的消息。成功返回0,失败返回-1signum:传输信号的序号
act:结构体 (*sa_handler)(int):与signal函数的handle相同
(*sa_sigaction)(int, siginfo_t *, void *):为函数指针。int为信号的序号;void*是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息
sa_mask:sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号
sa_flags:通常使用SA_SIGINFO,使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
oldac:为记录信号之前的内容。
int sigqueue(pid_t pid, int sig, const union sigval value);只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。pid:接收信号的进程id
sig:发送信号的序号
value:联合体,指定信息传递的参数。

  • 实战

发送

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

//       int sigqueue(pid_t pid, int sig, const union sigval value);

int main(int argc,char **argv)
{
        if(argc != 3){
                printf("put data number error!\n");
                exit(1);
        }

        int pid;
        int signum;

        pid = atoi(argv[2]);
        signum = atoi(argv[1]);

        union sigval value;
        value.sival_int = 100;

        sigqueue(pid,signum,value);

        printf("done!\n");

        return 0;
}

接受

#include <stdio.h>
#include <signal.h>

//       int sigaction(int signum, const struct sigaction *act,
//                     struct sigaction *oldact);

void handle(int signum, siginfo_t *info, void *context)
{
        printf("get signal = %d\n",signum);

        if(context != NULL){
                printf("get Signal number = %d\n",info->si_signo);
                printf("get data = %d\n",info->si_int);
                printf("get value data = %d\n",info->si_value.sival_int);
        }
}

int main()
{
        struct sigaction act;

        act.sa_sigaction = handle;
        act.sa_flags = SA_SIGINFO;

        sigaction(SIGUSR1,&act,0);

        printf("pid = %d\n",getpid());

        while(1);
        return 0;
}

六、信号量

  • 信号量概述

    1. 信号量(semaphore)是操作系统中用来解决并发中的互斥和同步问题的一种方法
    2. 最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。Linux下的函数都是对通用信号量进行操作。
  • 信号量简述

    • 信号量就是所谓的钥匙,假设只有一把钥匙,当有人拿走了这把钥匙,那么另一个人就无法打开这扇门需要等待拿走这把钥匙的人将钥匙重新放回来,才可以打开这扇门。信号量就是防止过多的进程访问同一个公共资源。
  • 相关函数介绍

    1. semget:创建或获取一个信号量组。
      1. int semget(key_t key, int nsems, int semflg);
      2. 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
      3. semget创建新的信号量集合时,必须指定集合中信号量的个数(即nsems),通常为1; 如果是引用一个现有的集合,则将nsems指定为 0 。
    2. semop:对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1。
      1. int semop(int semid, struct sembuf *sops, unsigned nsops);
      2. 函数参数参数意义
        semid信号量集的标识符,一般由semget返回的。
        *sops     sembuf结构体的数组指针short sem_num:信号量组中对应的序号,0~sem_nums-1
        short sem_op:信号量值在一次操作中的改变量(一般为+1、-1)
        short sem_flg:IPC_NOWAIT, SEM_UNDO(一般选择UNDO阻塞)
        nsops为sops中的元素个数
    3. semctl:控制信号量的相关信息,与消息队列中的msgctl相似
      1. int semctl(int semid, int semnum, int cmd, union semun arg);
      2. cmd:
        1. IPC_STAT:获得该信号量的semid_ds结构,并存放在第四个参数arg结构变量的buf域指向的semid_ds结构
        2. IPC_SETVAL:将信号量值设置为arg的val值
        3. IPC_GETVAL:返回信号量当前值
        4. IPC_RMID:从系统中删除信号量
      3. 函数参数参数意义
        semid信号量集的标识符
        semnum信号量组中对应的序号
        cmd

        SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。对于刚创建的新信号量必须要进行设置。对于使用同一个key的只需要设置一次即可。

        IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
        union semun该函数有三个或四个参数,而几个参数是由cmd决定的,该参数就是第四个参数。联合体。如果使用该参数,则其类型为semun。

实战

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

union semun {
       int              val;    /* Value for SETVAL */
       struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
       unsigned short  *array;  /* Array for GETALL, SETALL */
       struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                   (Linux-specific) */
};

void pGetKey(int semid)
{
        struct sembuf set;

        set.sem_num = 0;
        set.sem_op = -1;
        set.sem_flg = SEM_UNDO;

        semop(semid,&set,1);

        printf("get key!\n");
}

void vPutKey(int semid)
{
        struct sembuf set;

        set.sem_num = 0;
        set.sem_op = +1;
        set.sem_flg = SEM_UNDO;

        semop(semid,&set,1);

        printf("put back key!\n");
}

int main()
{
        int semid;
        int key;
        int pid;

        key = ftok(".",2);

        semid = semget(key,1,IPC_CREAT|0666);

        union semun initsem;

        initsem.val = 0;
        semctl(semid,0,SETVAL,initsem);

        pid = fork();

        if(pid > 0){
                pGetKey(semid);
                printf("this is father!\n");
                vPutKey(semid);
        }
        if(pid == 0){
                printf("this is child!\n");
                vPutKey(semid);
        }

        semctl(semid,0,IPC_RMID,initsem);

        return 0;
}

七、ipcs指令与ipcrm指令

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值