linux进程间通信

1、进程间通信相关介绍

一、进程间通信概念:

进程间通信(IPC)是指在不同进程之间传播或交换信息;每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

二、进程间通信的目的

数据传输: 一个进程需要将它的数据发送给另一个进程。

资源共享: 多个进程之间共享同样的资源。

通知事件: 一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件,比如进程终止时需要通知其父进程。

进程控制: 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

三、常见的进程间通信方式

1、管道(pipe)(包括无名管道和命名管道)

2、消息队列

3、共享内存

4、信号(signal)

5、信号量

二 、管道

一、无名管道(pipe)

  1. 特点

pipe 为半双工的,适用于一对一的、具有亲缘关系的进程间的通信(父子进程或者兄弟进程之间)。

  1. 函数原型

当一个管道建立时,它会创建两个文件描述符:一个专用于读fd[0],一个专用于写fd[1],管道用于不同进程间通信,通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。

  1. 编程实现

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{

        int fd[2];//定义文件操作符
        int pid;
//      int pipe(int pipefd[2]);

        char buf[128];

        if(pipe(fd) == -1){
                printf("creat pipe failed!\n");//判断无名管道是否创建成功
        }

        pid= fork();//创建子进程

        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 from father",strlen("hello  from father"));//父进程进行写操作              wait();//等待子进程退出
        }else{
                printf("this is child\n");
                close(fd[1]);//关闭写操作
                read(fd[0],buf,128);//子进程进行读操作
                printf("read from father:%s\n",buf);
                exit(0);
        }
        return 0;

}

二、有名管道(FIFO)

  1. 特点

FIFO可以在无关的进程之间交换数据,与无名管道不同。FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

  1. 函数原型

pathname:FIFO文件名

mode:属性。一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO,若以只读和只写方式 open 管道,并指定非阻塞标志 O_NONBLOCK ,则 open() 函数不会阻塞。

  1. 编程实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main()
{

//      int mkfifo(const char *pathname, mode_t mode);
        if( (mkfifo("./file",0600) == -1) && errno != EEXIST){

                printf("mkfifo failed\n");
                perror("why");
        }

        int fd=open("./file",O_RDONLY);
        printf("open success\n");
        return 0;

}

三、消息队列( Message Queue )

  1. 特点

消息队列是一种进程间通信或同一进程的不同线程间的通信方式。

进程或者线程之间通过消息进行通信,消息发送后可以立即返回,由消息系统来确保信息的可靠传递,消息发布者(生产者)只管把消息发布到消息队里中而不用管谁来消费,消息使用者(消费者)只管从消息队列中获取消息以进一步处理而不用管理谁发布的消息,这样发布者和使用者都不用知道对方的存在。

  1. 函数原型

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  1. 代码实现

#include <stdio.h>

#include <string.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()
{

        //1.huoqu
        struct msgbuf sendBuf={888,"this is message from quen"};
        struct msgbuf readBuf;

        key_t key;//获取key值
        key= ftok(".",'z');
        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);//

        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
        printf("read from que:%s\n",readBuf.mtext);
        msgctl(msgId,IPC_RMID,NULL);

        return 0;
}
四、共享内存

1.特点

共享内存就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。共享内存是最快的一种 IPC,因为进程是直接对内存进行存取,因为多个进程可以同时操作,所以需要进行同步。信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

2.函数原型

#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
//ipcs -m  查看系统有哪些共享内存            ipcrm -m 65536删除共享内存

3.代码实现

shmw.c

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


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

        key_t key;//获取key的值
        key=ftok(".",1);

        shmid=shmget(key,1024*4,IPC_CREAT|0666);//创建共享内存
        if(shmid == -1){
                printf("shmget failed!\n");
                exit(-1);
        }
        exit(0);
        shmaddr=shmat(shmid,0,0);//连接映射共享内存
        printf("shmat ok!\n");
        strcpy(shmaddr,"zhengyanfeng");//数据交互

        sleep(5);
        shmdt(shmaddr);//断开与共享内存连接
        shmctl(shmid,IPC_RMID,0);//删除共享内存
        printf("quit\n");
        return 0;

}

shmr.c

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

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

        key_t key;//获取key的值
        key=ftok(".",1);

        shmid=shmget(key,1024*4,0);//创建共享内存
        if(shmid == -1){
                printf("shmget failed!\n");
                exit(-1);
        }

        shmaddr=shmat(shmid,0,0);//连接映射共享内存
        printf("shmat ok!\n");//数据交互
        printf("data: %s\n",shmaddr);


        shmdt(shmaddr);//断开与共享内存连接

        printf("quit\n");
        return 0;

}
五、信号
  1. 特点

信号是一种事件通知机制,当接收到该信号的进程会执行相应的操作。在 Linux 操作系统中, 为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过 kill -l 命令,查看所有的信号:

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

运行在 shell 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号,例如:

Ctrl+C 产生 SIGINT 信号,表示终止该进程。

如果进程在后台运行,可以通过 kill 命令的方式给进程发送信号,但前提需要知道运行中的进程 PID 号,例如:

kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程。

所以,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)。

信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。
1.执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 SIGTERM 信号,就是终止进程的意思。Core 的意思是 Core Dump,也即终止进程后,通过 Core Dump 将当前进程的运行状态保存在文件里面,方便程序员事后进行分析问题在哪里。
2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。
3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP,它们用于在任何时候中断或结束某一进程。
  1. 函数原型

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

第一个参数signum:指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。  

第二个参数handler:描述了与信号关联的动作,它可以取以下三种值: 

SIG_IGN这个符号表示忽略该信号。SIG_DFL这个符号表示恢复对信号的系统默认处理。不写此处理函数默认也是执行系统默认操作以及sighandler_t类型的函数指针。

  1. 代码实现

include <stdio.h>
#include <signal.h>
//       typedef void (*sighandler_t)(int);
//       sighandler_t signal(int signum, sighandler_t handler);

void handler(int signum)
{

        printf("get signum:%d\n",signum);
        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSER1\n");
                        break;
        }

}
int main()
{
        signal(SIGINT,SIG_IGN);
        signal(SIGKILL,SIG_IGN);
        signal(SIGUSR1,handler);
        while(1);

        return 0;
}
include <stdio.h>
#include <signal.h>
#include <sys/types.h>

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


int main(int argc,char **argv)
{

        int signum;
        int pid;

        char cmd[128]={0};

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

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

//      kill(pid,signum);
        sprintf(cmd,"kill -%d %d",signum,pid);
        system(cmd);
        printf("send signal ok\n");
        return 0;
}
  1. 信号发送接收函数

sigaction接收函数原型:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
 
struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

sigqueue接收函数原型:

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

sigactionRead.c

include <signal.h>
#include <stdio.h>
//       int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

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;  //be able to get message


        sigaction(SIGUSR1,&act,NULL);
        while(1);
        return 0;

sigqueueSend.c

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(int argc, char **argv)
{
    int signum;
    int pid;
    //int a = 10;
    
    signum = atoi(argv[1]);
    pid = atoi(argv[2]);
 
    union sigval value;
    value.sival_int = 100;
    //strcpy(value.sival_ptr,"vale");
    //value.sival_ptr=&a;    
    // int sigqueue(pid_t pid, int sig, const union sigval value);
    sigqueue(pid,signum,value);
    printf("%d,done\n",getpid());
 
    return 0;
}
六、信号量
  1. 特点

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。支持信号量组。

  1. 函数原型

#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
  1. 代码实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//联合体,用于semctl初始化
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 vPutKey(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 semid;

        key=ftok(".",2);


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

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

        int pid =fork();
        if(pid >0){
                pGetKey(semid);

                printf("this is father\n");
                vPutKey(semid);
                semctl(semid,0,IPC_RMID);
        }
        else if(pid ==0){

                printf("this is child\n");
                vPutKey(semid);
        }else{

                printf("fork error\n");
        }



        return 0;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值