Linux进程间通信

277 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第36天,点击查看活动详情

1、通信技术IPC(InterProcess Communication)

1.IPC方式有: *管道(包括无名管道和命名管道) *消息队列 *信号量 *共享存储 *Socket *Streams Socket和Streams支持多机通信

2、无名管道

1.半双工,只在父子进程之间,管道可以看成特殊文件用read write读写只存在于内存不生成文件。 2.原型

int pipe(int fd[2])

返回值:成功0,失败-1 管道建立成功生成2个文件描述符,fd[0]读,fd[1]写 3.用法举例 建立管道:pipe(fd),判断是否成功 创建子进程:fork,判断是否成功 在父子进程读写:父进程写,子进程读 父:close(fd[0]),write(fd[1],内容,个数) 子:close(fd[1]),read(fd[0],,) 注:写时要关闭读管道,读时要关闭写管道 读写方向一但固定就无法改变。

3、命名管道(FIFO)

1.半双工,可以无关进程之间,以特殊文件形式存在文件系统 2.原型

int mkfifo(const char* pathname,mode_t mode)

返回值:成功0 ,失败-1,可以用perror查看失败原因 参数:和open一样,文件路径名,0600(读写) 参数1:可设置是否阻塞O_NONBLOCK,默认是阻塞。

3.例: *进程1 open打开管道 read阻塞等待write(要用while) 输出读的数据 close关闭管道

*进程2 open打开管道 write写数据 close关闭管道 注:管道文件数据读后立马清空,先进先出

4、消息队列

4.1.

全双工,链表在内核中有标识符(队列ID),随机查询按类型读取,进程终止内容不会删除

4.2.API

int msgget(key_t key,int flag)

创建消息队列,成功返回0,失败-1 参数1:key可以用ftok函数,也可以直接写0x1234 参数2:创建队列IPC_CREAT,打开方式可读可写可执行0777

int msgsnd(int msqid,const void*ptr,size_t size,int flag)

添加消息,成功0失败-1 参数1:队列号key 参数2:结构体指针

struct msgbuf
{
     long mtype;
     char mtext[1];
}

参数3:个数用strlen算 参数4:0阻塞

int msgrcv(int msqid,void*ptr,size_t size,long type,int flag)

读取消息,成功返回数据长度 参数1:队列号 参数2:结构体指针 参数3:个数 参数4:消息类型,结构体的mtype 参数5:0阻塞等待

int msgctl(int msqid,int cmd,struct msqid_ds*buf)

控制消息队列,释放移除队列 参数1:队列号 参数2:常用IPC_RMID,释放移除队列 参数3:通常NULL

4.3.ftok函数(通常用于消息队列)

key_t ftok(const char*fname,int id)

返回值:key,队列号 参数1:当前目录 参数2:id 例:key=ftok(".",'a')

5、共享内存

1.共用一个内存,写读用指针指向这个内存 一读一写,前面内容销毁 2.API

int shmget(key_t key,size_t size,int flag)

创建打开一个共享内存 返回值:成功返回id,失败返回-1 参数1:key,用ftok 参数2:创建内存大小,必须以m为单位 参数3:创建共享内存IPC_CREAT,可读写0666 例:shmip=shmget(key,1024*4,IPC_CREAT|0666)

void *shmat(int shm_id,const void* addr,int flag)

连接共享内存地址空间 返回值:成功返回地址,失败-1 参数1:共享内存id 参数2:0 参数3:0 注:这个指针不需要free释放

int shmdt(void* addr)

断开连接 返回值:成功0失败-1 参数:id

int shmctl(int shm_id,int cmd,struct shmid_ds* buf)

控制共享内存,释放 返回值:成功0失败-1 参数1:id 参数2:IPC_RMID 参数3:0 3.例: 创建打开共享内存 连接映射共享内存 读写数据(用printf) 断开连接 释放干掉共享内存

6、信号

一、一些命令

1.用指令kill -l,查看信号名称和编号

2.忽略,捕捉,系统默认信号

3.使用

例:杀死进程

kill -信号编号 进程pid

二、信号注册,捕捉,忽略(入门)

1.API 函数:

tpyedef void(*sighandler_t)(int)

函数指针

sighandler_t signal(int signum,sighandler_t handler)

参数1:信号名称 参数2:函数指针 高级:sigaction()

2.用signal捕捉信号,然后用函数指针更改系统默认信号,改成自己要操作的内容。 例: 捕捉信号 进入函数指针里,操作要干什么 注:9 SIGKILL,系统信号改变不了

3.用函数main传参杀死进程 *API

int kill(pid_t pid,int sig)

发信号,杀死进程

参数:1.进程pid,2.信号编号加-

atoi()//char转int

参数:二级指针,mian形参 sprintf() 做字符串 参数:比printf前面多一个参数,char是指针型,做出来的字符串放到char*里面 *例: main完整有形参 把2个字符型形参用atoi转整型(pid,signum) 2种方法 用kill杀死进程 用sprintf做字符串,在用system杀死进程

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

int main(int argc,char **argv)
{
        int signum;
        int pid;
        char cmd[128] = {0};
        signum = atoi(argv[1]);
        pid = atol(argv[2]);
        sprintf(cmd,"kill %d %d",signum,pid);
        system(cmd);
        printf("send signal ok\n");
        return 0;
}
//使用:./a -9 2313

4.忽略信号 signal第二个参数写宏,SIG_ICN 注:忽略不了信号9 三、信号携带信息,收发(高级) 1.sigaction(捕捉信号)

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

参数1:信号编号 ★参数2:结构体指针 参数3:备份,NULL 一般用法:都配置

2.struct sigaction

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

成员1:和signal的函数指针一样 ★成员2:多个形参的函数指针 成员3:是否阻塞,默认阻塞 成员4:SA_SIGINFO 成员5:一般不用 一般用法:配成员2,4

3.函数指针handler() 参数1:int signum接受到的信号类型编号 参数2:结构体siginfo_t,读数据:si_int,pid,发消息:一个联合体,可以发整型和字符。 参数3:void*context指针,判断是否有数据传过来,无数据指向空NULL

例: sigaction函数 配置结构体2个成员 写handler函数 函数里判断有数据打印数据 发送 1.sigqueue

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

参数1:给谁发pid 参数2:发什么信号 参数3:数据,联合体union sigval(整型,字符)

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

例: 用完整main,2个参数转int 函数sigqueue 写数据联合体(特别注意段错误,共用一个内存,写字符的时候要用指针)

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

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 buf;

        buf.sa_sigaction = handler;
        buf.sa_flags = SA_SIGINFO;

        sigaction(SIGUSR1,&buf,NULL);

        while(1);
        return 0;
}
/*输出
get signum 10
get data:10
get data:10
from:48116
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char **argv)
{
        int signum;
        int pid;
        union sigval value;
        value.sival_int = 10;
        signum = atoi(argv[1]);
        pid = atol(argv[2]);
        sigqueue(pid,signum,value);
        printf("%ddone\n",getpid());
        return 0;
}

7、信号量(锁)

1.控制谁先运行 2.API

int semget(key_t key,int num_sems,int sem_flags)

创建,获取信号组 返回值:semid 参数:key,用flok 参数:信号组个数,一般 参数:IPC_CREAT|0666

int semop(int semid,struct sembuf *sops,size_t numops)

操作信号量,改变值 参数1:id 参数2:结构体指针可以有多个也可以是一个, 成员:sem_num,信号量编号0 成员:sem_op,操作信号量,-1,1 成员:sem_flg,SEM_UNDO 参数3:几个信号量

int semctl(int semid,int sem_num,int cmd,...)

控制信号量组相关信息 参数:id 参数:操作第几个信号量,0开始 参数:很多宏,常用SETVAL设置信号量值 IPC_RMID销毁 参数:联合体,union semun,开头要定义 初始化设置信号量值为1,联合体val=1 例: 创建信号量组 初始化信号量 创建父子进程 写2个函数,一个信号量加,一个减 子进程运行完信号量加 父进程先减然后运行在加 销毁锁

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.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 id)
{
        struct sembuf set;
        set.sem_num = 0;
        set.sem_op = -1;
        set.sem_flg = SEM_UNDO;
        semop(id,&set,1);
        printf("getkey\n");
}

void vPutBackKey(int id)
{
        struct sembuf set;
        set.sem_num = 0;
        set.sem_op = 1;
        set.sem_flg = SEM_UNDO;
        semop(id,&set,1);
        printf("put back the key\n");
}
int main()
{
        key_t key;
        int sem_id;
        key = ftok(".",'a');
        union semun initsem;
        initsem.val = 0;//锁初始没有钥匙

        sem_id = semget(key,1,IPC_CREAT|0666);
        semctl(sem_id,0,SETVAL,initsem);

        int pid = fork();
        if(pid > 0){
                pGetKey(sem_id);//父进程那不到,因为没有钥匙
                printf("this is father\n");
                vPutBackKey(sem_id);
        }else if(pid == 0){
                printf("this is child\n");
                vPutBackKey(sem_id);//子进程执行完,放钥匙进去
        }else
        {
                printf("fork error\n");
                exit(-1);
        }

        semctl(sem_id,0,IPC_RMID);
        return 0;
}