Linux进程通信

本文详细介绍了Linux系统中的进程间通信(IPC)技术,包括管道(普通和命名管道)、套接字(UNIX域和Internet)、SystemVIPC(消息队列、信号量和共享内存)以及信号。通过实例展示了如何创建、发送、捕获和处理信号,以及如何使用管道和消息队列进行数据交换。此外,还探讨了信号阻塞和信号处理函数的注册。这些技术对于理解和实现多进程间的协调至关重要。
摘要由CSDN通过智能技术生成

进程通信

进程间通信:指在不同进程间传递或交换信息。

在这里插入图片描述

IPC技术

Linux的三种主要通信方式:管道,套接字,System V IPC。

管道

普通管道:半双工,单向传送数据,且只能在同源进程间使用。

命名管道:FIFO队列,借助外存解除普通管道只能在同源进程间使用的限制。

System V IPC包括:消息队列、信号量和共享内存

套接字:分为UNIX域套接字和Internet套接字。

信号:不能称之为真正意义上的IPC,只是简单的事件通知,并不能完成数据传输。

信号

一种软中断,信号作为进程间通信的一种机制,由一个进程发送给另一个进程。

信号的生命周期:从创建一个信号,并且一直保存,直到内核可以根据这个信号做出相应动作为止,然后引发这个动作。

信号产生情况

  • 用户在终端按下一个组合键;

  • 硬件异常;

  • 进程调用kill函数发送信号;

  • 当检测到某软件异常时产生信号;

信号处理

忽略信号:不进行任何处理

捕获信号:定义信号处理函数,当信号发生时,执行相应的处理函数

执行默认动作:Linux为每种信号规定了默认操作

SIGKILL与SIGSTOP无法被捕捉和忽略,作用:管理员在任何时候都可以中断结束任意进程。

发送信号

kill调用

#include<sys/types.h>

#include<signal.h>

int kill(pid_t pid,int sig);//向指定进程发送sig信号

pid: >0:发送指定PID进程;=0:将信号发送给和目前进程所在进程组的所有进程;=-1:将信号广播给系统内所有进程;<-1:发送信号给PID的绝对值进程。

alarm定时器

#include<unistd.h>

int alarm(int seconds);//设定一个定时器,在指定秒数后向进程自身发送一个SIGALRM信号,该信号默认处理是终止当前进程。

seconds:设置为0,取消所有闹钟;否则覆盖前一个闹钟并返回剩余秒数。

函数返回值:0或者剩余秒数。

raise

#include<unistd.h>

int raise(int sig);//给自己发信号sig,等价与kill(getpid(),sig);

进程挂起

#include<unistd.h>
int pause(void);//挂起进程直到有信号到达
int sleep(int sec);//挂起进程直到有信号到达或过去sec秒,返回0或者剩余秒数

信号捕获与处理

sighandler_t signal(int signum,sighandler_t handler);//向内核注册指定信号的处理

signum:待捕获或忽略的信号

handler:捕获信号由系统回调的函数,特殊值:SIG_IGN:忽略信号;SIG_DFL:恢复默认

返回值:成功时返回原定义信号处理函数,失败返回:SIG_ERR

流程

  • 定义处理函数
  • 使用signal注册
  • 使用完后,恢复到默认处理:signal(signum,SIG_DFL)
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void fun_ctrl_z();
int main()
{
    (void) signal(SIGTSTP,fun_ctrl_z);//如果摁了<Ctrl>+Z,调用fun_ctrl_z函数
    printf("主程序:程序进入一个无限循环!\n");
    while(1)
    {
        printf("这是一个无限循环(要退出请按<Ctrl>+Z键!)\n");
        sleep(10);
    }
    return 0;
}
void fun_ctrl_z()//自定义信号处理函数
{
     printf("\t已进入自定义信号处理函数,再按<Ctrl>+Z退出\n");
     printf("\t此例不处理,重新恢复SIGINT信号的系统默认处理。\n");
     (void) signal(SIGTSTP,SIG_DFL);//恢复默认处理
}

信号阻塞

sigprocmask(int how,sigset_t *set,sigset_t *oldset)//设置或查询阻塞信号集

how

  • SIG_BLOCK:将set中的信号添加到阻塞信号集

  • SIG_UNBLOCK:将set中的信号从阻塞信号集中删除

  • SIG_MASK:将set信号集设置为阻塞信号集

set:新的信号集,若为NULL则忽略how,只是将原信号集保存到oldset

oldset:保存原信号集

int sigemptyset(sigset_t *set);//清空信号集
int sigfillset(sigset_t *set);//初始化信号集,并用系统所有有效信号填充到信号集
int sigaddset(sigset_t *set, int signo)//将指定信号添加到信号集
int sigdelset(sigset_t *set, int signo)//将指定信号从信号集删除
int sigismember(sigset_t *set int signo)//判断指定信号是否包含在信号集中。包含返回1;不包含返回0;失败返回-1
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
void fun_ctrl_c();
int main()
{
    int i;
    sigset_t set,pendset;
    struct sigaction action;
    (void) signal(SIGINT,fun_ctrl_c);
    if(sigemptyset(&set)<0)//获得一个空信号集
        perror("初始化信号集合错误");
    if(sigaddset(&set,SIGINT)<0)//向信号集加入<Ctrl>+C信号
        perror("<Ctrl>+C加入信号集合错误");
    if(sigprocmask(SIG_BLOCK,&set,NULL)<0)//将信号集加入到阻塞信号集
        perror("往信号阻塞集增加一个信号集合错误");
    else
    {
        for(i=0;i<5;i++)
        {
            printf("按下<Ctrl>+C试试,此处语句依旧显示\n");
            sleep(2);
        }
    }
    if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0)//将信号集从阻塞信号集移出
        perror("从信号阻塞集删除一个信号集合错误");
    return 0;
}
void fun_ctrl_c()
{
    printf("\t您按了<Ctrl>+C,但不予执行\n");
    (void) signal(SIGINT,SIG_DFL);
}

管道

pipe系统调用

int pipe(int fds[2]);

创建管道同时创建两个文件描述符,其中fds[0]已读打开,fds[1]以写打开。成功返回0,失败返回-1。

头文件:unistd.h

使用步骤如下:

  • 创建所需的管道;

  • 生成(多个)子进程;

  • 关闭/复制文件描述符,使之与相应的管道末端相联系;

  • 关闭不需要的管道末端;

  • 进行通信活动;

  • 关闭所有剩余的打开文件描述符

命令管道

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
int mkfifo(char *pname,mode_t mode);//创建一个命令管道,成功返回0,失败返回-1.
int mknod(const char *pname,mode_t mode,dev_t dev);//创建一个特殊文件

pname:路径

mode:新建管道文件的权限

dev_t:文件类型,S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO or S_IFSOCK

步骤:

  • 使用mkfifo或mkmod创建管道文件

  • 以只读或以只写方式打开(open)管道文件

  • 以read读取或write写管道

  • 关闭管道文件

默认管道的读写是阻塞的,而且只有两端都打开后才能进行读写。

/*send.c*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
int main(int argc,char *argv[]){
    mkfifo("tp",0644);//建立命令管道
    int infd;
    infd = open("test",O_RDONLY);//打开源文件
    if(-1 == infd){
        perror("open");
        exit(1);
    }
    int outfd;
    outfd = open("tp",O_WRONLY);//打开管道
    if(-1 == outfd){
        perror("open");
        exit(1);
    }
    char buf[1024];
    int n;
    while((n = read(infd,buf,1024)) > 0){
        write(outfd,buf,n);//写入管道
    }
    //关闭读端写端
    close(infd);
    close(outfd);
    return 0;
}
/*get.c*/
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
int main(int argc,char *argv[]){
    //写端
    int outfd;
    outfd = open("target.bak",O_WRONLY | O_CREAT | O_TRUNC,0644);
    //打开或建立目标文件
    if(-1 == outfd){
        perror("open");
        exit(1);
    }
    int infd;
    infd = open("tp",O_RDONLY);//只读打开命名管道
    if(-1 == infd){
        perror("open");
        exit(1);
    }
    char buf[1024];
    int n;
    while((n = read(infd,buf,1024))>0){
        write(outfd,buf,n);//写入目标文件
    }
    close(infd);
    close(outfd);
    unlink("tp");
    return 0;
}

消息对列

消息队列是链表队列,提供了一种从一个进程向另一个进程发送一个数据块的方法。

在程序中若要使用消息队列,必须要能知道消息队列key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪个消息队列,同时也要保证每个消息队列key值的唯一性。

struct msg{//消息体
    long mtype;//消息类型,固定项,必须大于0
    ···//自定义项
};
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
key_t ftok(char *path,int proj_id);//产生唯一的msgID
int msgget(key_t mkey,int msgflg);//创建、打开消息对列
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);//发送消息
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);//接受消息
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列

创建一个key

path:指定一个路径,在不同的进程中通过约定的path确保可产生相同的msgid。

proj_id:一个序数,取值在0-127之间。

创建、打开消息队列

key:消息队列的ID,若为IPC_PRIVATE或key代表的消息队列不存在且在msgflg中指明了IPC_CREAT。

msgflg:

  • IPC_CREAT:若消息队列不存在则创建,同时指定权限:IPC_CREAT|0666

  • IPC_EXCL:和IPC_CREAT配合使用,若消息队列存在则失败。

发送消息

把指定消息插入到指定的消息队列中

  • msqid:消息队列ID

  • msgp:消息体,应该是一个结构体指针

  • msgsz:发送消息的大小

  • msgflg:0或IPC_NOWAIT,0则若队列满则被阻塞;IPC_NOWAIT若队列满在立即返回错误。

接收消息

从指定消息队列接收消息

  • msqid:消息队列ID

  • msgp:消息体,应该是一个结构体指针

  • msgsz:接收消息的大小

  • msgtyp:接收那条消息,0:队列第一条消息;绝对值>0:接收|msgtype|与msgp的mtype相同的消息

  • msgflg:0:若没有消息则阻塞;IPC_NOWAIT:若没有消息则立即返回错误。

控制消息队列

  • msqid:消息队列ID

cmd的指定操作:

  • IPC_RMID:删除指定的消息队列,buf指定为NULL

  • IPC_STAT:将内核中的消息结构复制到用户空间中的buf中

  • IPC_SET:设置消息队列的有效UID和GID、操作权限等。

/*server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#define SERVER_MSG 20
#define CLIENT_MSG 21
#define NAMESIZE 20
#define QUIT "88"

struct msgbuf{
    long msg_type;
    char msg_user[20];
    char msg_time[30];
    char msg_text[100];
};
int main(){
    int qid;
    char username[NAMESIZE];
    key_t key;
    int len, rfd;
    fd_set read_set;
    struct msgbuf msg;
    struct timeval timeout;//这个是用来判断是否超时
    time_t t;
    if ((key=ftok(".",'a')) == -1){
        printf("failed to make a standard key.\n");
        exit(1);
    }
    if((qid=msgget(key,IPC_CREAT|0666)) == -1){
        printf("failed to get message queue");
        exit(1);
    }
    printf("给自己起个名字吧: ");
    scanf("%s",username);
    len = sizeof(msg.msg_text);
    rfd = fileno(stdin);
    fflush(stdin);
    printf("----------\n");
    fflush(stdout);
    while(1){
        if(msgrcv(qid, &msg, len,CLIENT_MSG,IPC_NOWAIT)>0 ){
        //读取来自client的消息
           printf("%s:%s\n%s\n",msg.msg_user,msg.msg_time,msg.msg_text);
           printf("----------\n");
           fflush(stdout);
        }
        FD_ZERO(&read_set);
        FD_SET(rfd, &read_set);
        timeout.tv_sec = 0;
        timeout.tv_usec = 500000;
        if(select(rfd+1, &read_set, NULL, NULL, &timeout) <= 0)
            continue;
        if(FD_ISSET(rfd, &read_set)){
            fgets(msg.msg_text, len, stdin);
            msg.msg_text[strlen(msg.msg_text) - 1]='\0';
            if( strlen(msg.msg_text) == 0)
                continue;
            if( strcmp(QUIT, msg.msg_text) == 0){//输入QUIT 结束聊天
                msgctl(qid,IPC_RMID,NULL);//删除消息
                break;
            }
            msg.msg_type = SERVER_MSG;//设置消息类型
            time(&t);
            strcpy(msg.msg_time, ctime(&t));
            strcpy(msg.msg_user, username);
            msgsnd(qid, &msg, len, IPC_NOWAIT);//队列未满则发送消息
            printf("----------\n");
            fflush(stdout);
        }

    }
    return 0;
}
//作为client与server相似,更改部分代码即可
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

registor11

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

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

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

打赏作者

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

抵扣说明:

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

余额充值