进程间通信-信号、共享内存

信号概述

  • 信号是UNIX中所使用的进程通信的一种最古老的方法。它是在软件层次上对中断机制的一种模拟,是一种异步通信方式。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止。
  • 一个完整的信号生命周期可以分为3个重要阶段,这3个阶段由4个重要事件来刻画的:包括信号产生、信号在进程中注册、信号在进程中注销、执行信号处理对应的函数 。


信号常见的处理函数

  • 信号的处理包括信号的发送、捕捉和处理,它们有各自相对应的常见函数:
    发生信号的函数: kill()、raise()。
    捕捉信号的函数: alarm()、pause()。
    处理信号的函数: signal()、sigaction()。


  • 信号发送函数
  • kill()函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令只是kill()函数的一个用户接口)。这里需要注意的是,它不仅可以中止进程(实际上发出SIGKILL信号),也可以向进程发送其他信号。
  • 与kill()函数所不同的是,raise()函数允许进程向自身发送信号。



  • 代码实战
    kill_raise.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>

int main()
{
    int ret;
    pid_t pid,pid_wait;
    pid=fork();
    if(pid<0)
    {
        printf("fork error!\n");
        return -1;
    }
    else if(pid==0)
    {
        printf("In child(%d) process,waiting for any signal\n",getpid());
        raise(SIGSTOP);
        exit(0);
    }
    else
    {
        
        if(pid_wait=waitpid(pid,NULL,WUNTRACED))
        {
            printf("Child(%d) is stop\n",pid_wait);
            if((ret=kill(pid,SIGKILL))==0)
                printf("In father process:\nParent kill process(%d)\n",pid);
        }
        
        /*
        if((waitpid(pid,NULL,WNOHANG))==0)
        {
            if((ret=kill(pid,SIGKILL))==0)
                printf("In father process:\nParent kill process(%d)\n",pid);
        }
        */
        waitpid(pid,NULL,0);
        exit(0);
    }
    return 0;
}
  • 信号捕捉函数
    alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。
    pause()函数是用于将调用进程挂起直至捕捉到信号为止。这个函数很常用,通常可以用于判断信号是否已到。



  • 信号处理函数
    信号处理的主要方法有两种,一种是使用简单的signal()函数,另一种是使用信号集函数组。


信号响应方式

  • 用户进程对信号的响应可以有3种方式。
    忽略信号,即对信号不做任何处理,但是有两个信号不能忽略,即SIGKILL及SIGSTOP。
    捕捉信号,定义信号处理函数,当信号发生时,执行相应的自定义处理函数。
    执行缺省操作,Linux对每种信号都规定了默认操作。

共享内存

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
  • 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
  • 进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
  • 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
  • Shell的ipcs命令可以查看共享内存情况


  • 共享内存使用步骤
    1)创建/打开共享内存;
    2)映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问;
    3)分离/撤销共享内存映射;
    4)删除共享内存对象;


  • 共享内存创建



    key : 和信号量一样,程序需要提供一个参数key,它有效地为共享内存段命名。有一个特殊的键值IPC_PRIVATE, 它用于创建一个只属于创建进程的共享内存,仅用于有亲缘关系的进程间通信。
    size: 以字节为单位指定需要共享的内存容量。
    shmflag: 包含9个比特的权限标志,它们的作用与创建文件时使用的mode标志是一样。
    读写的权限还有IPC_CREAT或IPC_EXCL对应文件的O_CREAT或O_EXCL
    权限标志对共享内存非常有用,因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其它用户创建的进程只能读取共享内存。
    我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,通过将数据放共享内存并设置它的权限,就可以避免数据被其他用户修改。

  • ftok
    同一段程序,为保证两个不同用户下的两组相同程序获得互不干扰的IPC键值,通常利用ftok函数将文件节点和一个id值组合生成一个唯一的IPC键值。
    ftok原型如下:key_t ftok( char * fname, int id )
    fname就时你指定的文件名(该文件必须是存在而且可以访问的),
    id是子序号,虽然为int,但是只有8个比特被使用(0-255)。
    当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。

    使用 ftok创建共享内存,毫无关系的进程也可以通过得到同样的key,来操作同一个共享内存,在对共享内存进行读写时,需要利用信号量进行同步或互斥。
    使用IPC_PRIVATE创建的IPC对象, 和无名管道类似,只可以用于有亲缘关系的进程间通信。
  • 共享内存映射函数
    作用:将共享内存映射到本进程地址空间,以实现本进程对该共享内存区的访问。
    注意:共享内存的读写权限由它的属主(共享内存的创建者)决定,它的访问权限由当前进程的属主决定。


  • 共享内存分离
    作用:将共享内存从当前进程空间分离。
    注意:共享内存分离并未删除它,只是使得该共享内存对当前进程不再可用。


  • 共享内存控制
    函数作用:实现对共享内存的控制



    shmid_ds结构至少包含以下成员:

struct shmid_ds  
{  
    uid_t shm_perm.uid;  
    uid_t shm_perm.gid;  
    mode_t shm_perm.mode;  
}; 
  • 共享内存代码实战:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define BUFFER_SIZE 1024

struct share_mm//共享内存结构体
{
    int flag_wrote;
    char buf[BUFFER_SIZE];
};

int main()
{
    int shmid;
    pid_t pid;
    struct share_mm * shmaddr;//保存映射地址
    //创建共享内存
    shmid = shmget(IPC_PRIVATE,sizeof(struct share_mm),0666);
    if(shmid==-1)
    {
        printf("shmget error\n");
        exit(1);
    }
    else
    {
        printf("Shmid is %d\n",shmid);
        system("ipcs -m");
    }
    //创建子进程
    pid = fork();
    if(pid==-1)
    {
        printf("fork error\n");
        exit(1);
    }
    else if(pid==0)//in child process
    {
        shmaddr=shmat(shmid,0,0);//映射,并获得映射地址
        if(shmaddr==(void *)-1)
        {
            printf("shmat error\n");
            exit(1);
        }
        else
        {
            printf("Child attach shm is %p\n",shmaddr);
            system("ipcs -m");
        }
        
        do
        {
            if(shmaddr->flag_wrote!=1)//判断父进程是否写数据到共享内存
            {
                printf("Wait father write message!!\n");
                while(shmaddr->flag_wrote!=1);//等待父进程写数据
                printf("From father message:%s\n",shmaddr->buf);
                shmaddr->flag_wrote=0;//标记数据已读走
            }
        }while(strncmp(shmaddr->buf,"quit",4));
        
        printf("Father byebye\n");
        if((shmdt(shmaddr))<0)//删除地址映射
        {
            printf("shmdt error\n");
            exit(1);
        }
        exit(0);
    }
    else //in fater process
    {
        shmaddr=shmat(shmid,0,0);
        if(shmaddr==(void *)-1)
        {
            printf("shmat error\n");
            exit(1);
        }
        else
        {
            printf("Father attach shm is %p\n",shmaddr);
            system("ipcs -m");
        }
        shmaddr->flag_wrote=0;
        do
        {
            if(shmaddr->flag_wrote==0)//判断数据是否被子进程读走
            {
                memset((void *)shmaddr->buf,0,BUFFER_SIZE);
                printf("In father process:\nPlease write message\n");
                gets(shmaddr->buf);//从键盘写入数据到共享内存
                shmaddr->flag_wrote=1;//标记已写数据
            }
        }while(strncmp(shmaddr->buf,"quit",4));
        
        waitpid(pid,NULL,0);//等待子进程先退出
        printf("Child byebye\n");
        if((shmdt(shmaddr))<0)
        {
            printf("shmdt error\n");
            exit(1);
        }
        if((shmctl(shmid,IPC_RMID,NULL))<0)//删除内核中的共享内存
        {
            printf("shmctl error\n");
            exit(1);
        }
        exit(0);
    }
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容