Linux系统编程之进程间通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、信号、共享内存、Socket。

一、管道

  1. 无名管道

  1. 特点

  • 半双工,具有固定的读端和写端;

  • 每次的读写操作,需要打开读端关闭写端,或者打开写端关闭读端;

  • 它只能用于具有父子进程或者兄弟进程之间;

  • 对于它的读写可以直接使用普通的read、write等函数;

  • 它不属于任何文件系统,并且只存在于内存。

  1. API原型

SYNOPSIS
    #include <unistd.h>
    
    int pipe(int pipefd[2]);
  1. API参数:一个int类型长度为2的数组,[0]为读区,[1]写区;

  1. API返回值:成功返回0,失败返回-1;

  1. demo编写

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

int main()
{
    int fd[2];      //fd[0]读区,fd[1]写区
    int pid;
    char readBuf[128];
    //创建无名管道
    if(pipe(fd) == -1){
        printf("creat pipe failed...\n");
    }
    //创建子进程
    pid = fork();
    //返回值为-1时,创建失败
    if(pid < 0){
        printf("creat child failed...\n");
    }
    //父进程,返回值是子进程的进程号
    else if(pid > 0){
        sleep(3);
        printf("this is father \n");
        //关闭父进程的读区
        close(fd[0]);
        //将内容写道父进程的写区里面
        write(fd[1],"hello form father",strlen("hell form father"));
        wait(NULL);
    }
    //子进程,返回值是0
    else{
        printf("this is child \n");
        //关闭写区
        close(fd[1]);
        //读取子进程读区的内容到readBuf[]里面
        read(fd[0],readBuf,128);
        printf("read from father : %s\n",readBuf);
        exit(0);
    }
    return 0;
}
  1. demo运行

  1. 有名管道/fifo

  1. 特点

  • fifo可以在无关的进程之间交换数据,与无名管道不同;

  • fifo有路径名与之相关联,以一种特殊设备文件存在于内存;

  • 类似于在进程中使用文件中来传输数据,在数据读出是,fifo管道中同时清理数据清理,有着“先进先出”的原则。

  1. API原型

SYNOPSIS
    #include <sys/types.h>
    #include <sys/stat.h>
    
    int mkfifo(const char *pathname, mode_t mode);
  1. API参数

  • pethname:管道的路径名称

  • mode:与open函数中的 mode 相同

  1. API返回值

  • 成功:返回0;

  • 出错:返回-1。

  1. demo编写(创建管道)

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

int main()
{
    //创建一个有名管道,返回值为-1时出错
    if(mkfifo("./file",0600) == -1){
        //通过捕捉错误码,进一步筛选
        if(errno != EEXIST){
            printf("mkfifo failed!\n");
            perror("why");
        }
        else if(errno == EEXIST){
            printf("file had\n");
        }
    }
    //否则输出创建成功
    else{
        printf("mkfifo success\n");
    }
    return 0;
}
  1. demo运行

  1. demo编写(读写双方程序读写)

//read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode)

int main()
{
    char readBuf[1024] = {0};
    int fd;
    while(1){
        //以只读的方式打开fifo,进程会进入阻塞,当其他进程进行写的时候,才会往下跑
        fd = open("./file",O_RDONLY);
        printf("open success\n");
        int n_read = read(fd,readBuf,1024);
        printf("read %d byte from fifo,context : %s\n",n_read,readBuf);
        close(fd);
    }
    return 0;
}
//write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

int main()
{
    int cnt = 0;
    char *str = "this message from fifo";
    int fd = open("./file",O_WRONLY);
    printf("write open success\n");
    while(cnt++ != 5){
        sleep(1);
        printf("%d\n",cnt);
        write(fd,str,strlen(str));
    }
    close(fd);
    return 0;
}
  1. demo运行

二、消息队列

  1. 特点

  • 消息队列,消息的链接表,存放在内核当中,一个消息队列由一个标识符来识别;

  • 消息队列,是面向于记录的,具有特定的格式和优先级;

  • 消息队列,独立于发送和接收进程。进程终止时,消息队列及其内容不会被删除;

  • 消息队列,支持随机查询,也可以按照消息的类型查询。

  1. API原型

SYNOPSIS
    #include <sys/msg.h>
    //创建或打开消息队列
    int msgget(key_t key, int msgflg);
    
    //添加消息
    ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
    
    //读取消息
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    
    //控制消息队列
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  1. API参数

  • msgget(创建或者打开消息队列)

  • key:消息队列对象的key(标识符),通常用ftok获取;

  • msgflg:消息队列的访问权限,msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符;

  • 返回值:返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。

  • msgrcv(读区消息)

  • msqid:消息队列的key(标识符);

  • msgp:读取消息的结构体;

struct msgbuf{

long mgytpe;//消息类型

char mtext; //消息正文

}

  • msgsz:要读取消息的大小;

  • msgtyp:

  • 0:接收第一个消息

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

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

  • msgflg:

  • 0:阻塞,没有该消息就会一直等待阻塞;

  • IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG;

  • IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息;

  • IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息。

  • 返回值:实际读取到消息的实际长度。

  • msgsnd(写入消息)

  • msqid:消息队列的key(标识符);

  • msgp:存放消息队列的结构体,与msgrcv发送的类型相同;

  • msgsz:要写入消息的大小;

  • msgflg:

  • 0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列;

  • IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回;

  • IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程;

  • 返回值:成功返回0,失败返回-1,并设置errno。

  • msgctl(控制消息队列)

  • msqid:消息队列的key(标识符);

  • msd:

  • IPC_STAT:获得msgid的消息队列头数据到buf中;

  • IPC_STAT:获得msgid的消息队列头数据到buf中;

  • buf:消息队列管理结构体.

  • 返回值:成功返回0,失败返回-1,并设置errno。

  1. demo编写(双方)

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

typedef struct msgBuf{
    long mtype;
    char mtext[128];
}msgBuf;

int main()
{
    msgBuf readBuf;
    msgBuf sendBuf = {988,"thank you for reach"};
    key_t key;
    key = ftok(".",123);        //

    printf("key = %x\n",key);

    int msgID = msgget(key,IPC_CREAT|0777);
    if(msgID == -1){
        printf("get que failed\n");
    }

    msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),888,0);
    printf("read from que : %s\n",readBuf.mtext);

    msgsnd(msgID,&sendBuf,strlen(sendBuf.mtext),0);
    printf("send over\n");

    msgctl(msgID,IPC_RMID,NULL);

    return 0;
}
//msgsend.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

typedef struct msgBuf{
    long mtype;
    char mtext[128];
}msgBuf;

int main()
{
    msgBuf sendBuf = {888,"this is message from quen"};
    msgBuf readBuf;
    key_t key;
    //"."指的是当前文件夹,123为子序列号
    key = ftok(".",123);
    printf("key = %x\n",key);    

    int msgID = msgget(key,IPC_CREAT|0777);
    if(msgID == -1){
        printf("get que failed\n");
    }

    msgsnd(msgID,&sendBuf,strlen(sendBuf.mtext),0);
    printf("send over\n");

    msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),988,0);
    printf("return from get : %s\n",readBuf.mtext);

    msgctl(msgID,IPC_RMID,NULL);//释放掉队列
    return 0;
}
  1. demo运行

三、共享内存

  1. 特点

  • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取;

  • 因为多个进程可以同时操作,所以需要进行同步;

  • 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

  1. API原型

SYNOPSIS
    #include <sys/types.h>
    #include <sys/stat.h>
    
    //创建共享内存
    int shmget(key_t key, size_t size, int shmflg);

    //映射共享内存
    void *shmat(int shmid, const void *shmaddr, int shmflg);

    //断开共享内存连接
    int shmdt(const void *shmaddr);

    //控制共享内存
    int shmctl(int shmid,int cmd,struct shmid_ds *buf);
    
  1. API参数

  • shmget(创建共享内存)

  • key:创建内存的key(标识符),可以通过ftok生成;

  • size:共享内存的大小;

  • shmflg:读写的权限,与open的mode相同;

  • 返回值:成功返回共享内存的ID,失败返回-1并设置errno。

  • shmat(映射共享内存)

  • shmid:共享内存的ID;

  • shmaddr

  • 0,系统自动分配地址,一般使用该项;

  • 非0,没有指定SHM_RND则连接到addr所指定的地址上;

  • 非0,指定SHM_RND则连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上;

  • shmflg:SHM_RDONLY(只读),

SHM_WRONLY(只写),

SHM_RDWR(可读可写);

  • 返回值:成功返回指向共享内存的指针,失败返回-1并设置errno。

  • shmdt(断开共享内存)

  • shmaddr:共享内存的指针;

  • 返回值:成功返回0,失败返回-1。

  • shmctl(控制共享内存)

  • shmid:共享内存的ID

  • cmd:

  • IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中;

  • IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内;

  • IPC_RMID:删除这片共享内存;

  • struct shmid_ds *buf:内存管理的结构体;

  • 返回值:成功返回0,失败返回-1并设置errno。

  1. demo编写

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

int main()
{
    char *shmaddr;
    key_t key;
    key = ftok(".",123);
    
    //创建共享内存
    int shmID = shmget(key, 1024*4, IPC_CREAT|0666);
    if(shmID == -1){
        printf("shmget failed!\n");
        
    }

    //映射共享内存
    shmaddr = shmat(shmID, 0, 0);//第一个0,是linux内核自动分配内存,
                         //第二个0,是设置成可读可写的模式
    //写入共享内存
    strcpy(shmaddr,"ErHui");
    sleep(5);

    //卸载共享内存
    shmdt(shmaddr);
    
    //去掉共享内存
    shmctl(shmID,IPC_RMID,0);

    printf("quit");
    
    return 0;
}
//shmr.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main()
{
    char *shmaddr;
    key_t key;
    key = ftok(".",123);
    //创建共享内存
    int shmID = shmget(key, 1024*4, 0);
    if(shmID == -1){
        printf("shmget failed!\n");
        
    }

    //映射共享内存
    shmaddr = shmat(shmID, 0, 0);//第一个0,是linux内核自动分配内存,
                         //第二个0,是设置成可读可写的模式
    //读取
    printf("data : %s\n",shmaddr);

    //卸载共享内存
    shmdt(shmaddr);
    printf("quit");
    
    return 0;
}
  1. demo运行

四、信号

  1. 特点:软中断的信号

  1. 可以通过kill -l命令查看系统所有的信号。 目前Linux支持64种信号。 信号分为非实时信号 (不可靠信号)和实时信号(可靠信号)两种类型,对应于 Linux 的信号值为 1-31 和 34-64

  1. API原型

SYNOPSIS
    #include <signal.h>
    
    sighandler_t signal(int signum, sighandler_t handler);
  1. API参数

  • signum:信号的类型

  • handler:信号关联的动作

  • SIG_ING:忽略该信号

  • SIG_DFL:恢复信号的系统默认处理,不写就是系统默认操作;

  • sighandler_t类型的函数指针。

  1. demo编写()

//demo1是去捕捉这些信号
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

//typedef void (*sighandler_t)(int);函数指针
//sighandler_t signal(int signum, sighandler_t handler);

//强制杀死进程
//1.用 ps -aux|grep a.out ,查看当前运行的进程的ID号
//2.用 kill -9 [ID号],即可
//3.或者用 kill -SIGKILL [ID号],

void handler(int signum)
{
    printf("get signum=%d\n",signum);
    switch(signum){
        case SIGINT:
            printf("SIGINT\n");
            break;
        case SIGKILL:
            printf("SIGKILL\n");
            break;
        default:
            break;
    }
}

int main()
{
    printf("pid : %d",getpid())
    signal(SIGINT,handler);
    signal(SIGKILL,handler);
    while(1);
    return 0;
}
//demo2负责将上面这个进程杀死
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

//int kill(pid_t pid,int sig)

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

    pid = atoi(argv[1]);
    signum = atoi(argv[2]);// 字符串转换
    printf("num = %d , pid = %d\n",signum,pid);

    sprintf(cmd,"kill -%d %d",signum,pid);//字符串格式化

    //kill(pid, signum);
    system(cmd);//使用system函数执行脚本

    printf("send signal ok\n");
    return 0;
}

五、信号量

  1. 特点:

  1. API原型

SYNOPSIS
    #include <signal.h>
    
    int sigqueue(pid_t pid, int sig, const union sigval value);

    union sigval{
          int sival_int;  
          void *sival_ptr;  
     }; 

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

    struct sigaction{
        void (*sa_handler)(int);
        void (*sa_sigaction)(int, siginfo_t *, void *);
        sigset_t sa_mask;
        int sa_flags;
        void (*sa_restorer)(void);
    }

    siginfo_t {
        int si_signo; /* Signal number */
        int si_errno; /* An errno value */
        int si_code; /* Signal code */
        int si_trapno; /* Trap number that caused*/
        pid_t si_pid; /* Sending process ID */
        uid_t si_uid; /* Real user ID of sending process */
        int si_status; /* Exit value or signal */
        clock_t si_utime; /* User time consumed */
        clock_t si_stime; /* System time consumed */
        sigval_t si_value; /* Signal value */
        int si_int; /* POSIX.1b signal */
        void *si_ptr; /* POSIX.1b signal */
        int si_overrun; /* Timer overrun count; POSIX.1b timers */
        int si_timerid; /* Timer ID; POSIX.1b timers */
        void *si_addr; /* Memory location which caused fault */
        long si_band; /* Band event (was int inglibc 2.3.2 and earlier) */
        int si_fd; /* File descriptor */
        short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */
}
  1. API参数

  • sigqueue:(向一个进程发送信号)

  • pid:进程的pid;

  • sig:发送的信号;

  • const union sigval value:联合结构体;

  • 返回值:成功0,出错返回-1,并且设置errno值。

  • sigaction:

  • signum:要捕捉的信号;

  • const struct sigaction *act:接收到信号之后对信号进行处理的结构体

  • struct sigaction *oldact:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来

做备份)。如果不需要备份,此处可以填NULL;

  • sa_handler和sa_sigaction函数一次只能使用其中一个

  • 如果只是想处理信号,但是不获取信号信息,就使用sa_handler函数;

  • 如果想要处理信号,并且获取信号的各种信息,就使用sa_sigaction函数;

  • 返回值:成功0,出错返回-1,并且设置errno值。

  1. demo编写

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

//接收端

//void (*sa_sigaction)(int, siginfo_t *, void *)
void handler(int signum, siginfo_t *info, void *context)
{
    printf("get signum %d\n",signum);
    if(context != NULL){
        printf("get data=%d\n",info->si_int);
        printf("get data=%d\n",info->si_value.sival_int);
        printf("from : %d\n",info->si_pid);
    }
}

int main()
{
    struct sigaction act;
    printf("pid : %d\n",getpid());
    act.sa_sigaction = handler;
    act.sa_flags = SA_SIGINFO;

    sigaction(SIGUSR1,&act,NULL);
    while(1);
    return 0;
}
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

//发送端

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

int main(int argc,char **argv)
{
    int signum = atoi(argv[1]);
    int pid = atoi(argv[2]);

    union sigval value;
    value.sival_int = 100;
    sigqueue(pid, signum, value);
    printf("%d : done\n",getpid());
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二灰酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值