进程间通信IPC(共享内存,消息队列,信号灯)和信号的具体实现(下)

简介: 进程间通信IPC(共享内存,消息队列,信号灯)和信号的具体实现

信号灯(semaphore)

临界资源

  1. 一次只允许一个进程使用的资源称为临界资源;
    临界资源并不全是硬件或是软件,而是两者都能作为临界资源。
    比如硬件的有:打印机、磁带机等;
    软件有: 消息缓冲队列、变量、数组、缓冲区等;
  2. 临界区(critical region)

访问共享变量的程序代码段称为临界区,也称为临界段(criticalsection) ;

  1. 进程互斥

两个或两个以上的进程不能同时进入关于同一组共享变量的临界区,否可可能会发生与时间有关的错误,这种现象称为进程互斥;

semaphore

信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。信号灯种类:

  • posix有名信号灯(可用于线程、进程同步)
  • posix基于内存的信号灯(无名信号灯)
  • System V信号灯(IPC对象)

System V信号灯

System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。

而Posix信号灯指的是单个计数信号灯

System V 信号灯由内核维护

二值信号灯、计数信号灯

二值信号灯:

• 值为0或1。与互斥锁类似,资源可用时值为1,不可用时值为0

计数信号灯:

• 值在0到n之间。用来统计资源,其值代表可用资源数

PV操作

通常把信号量操作抽象成PV操作

P

等待操作是等待信号灯的值变为大于0,然后将其减1;

V

释放操作则相反,用来唤醒等待资源的进程或者线程

sem函数

semget

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

函数参数

key:和信号灯集关联的key值

nsems: 信号灯集中包含的信号灯数目

semflg:信号灯集的访问权限,通常为IPC_CREAT | 0666

函数返回值

成功:信号灯集ID

出错:-1

semctl

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl ( int semid, int semnum,  int cmd…/union semun arg/);

函数参数

semid:信号灯集ID

semnum: 要修改的信号灯编号

cmd:

GETVAL:获取信号灯的值

SETVAL:设置信号灯的值

IPC_RMID:从系统中删除信号灯集合

union semun {
short val;                       /SETVAL用的值/
struct semid_ds* buf;   /IPC_STAT、IPC_SET用的semid_ds结构/ unsigned short* array;
/SETALL、GETALL用的数组值/
struct seminfo *buf;     /为控制IPC_INFO提供的缓存/
} arg;

函数返回值

成功:0

出错:-1错误原因存于errno中

semop

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);

函数参数

semid:信号灯集ID

struct sembuf {
short  sem_num;  //  要操作的信号灯的编号
short  sem_op;   //    0 :  等待,直到信号灯的值变成0
//   1  :  释放资源,V操作
//   -1 :  分配资源,P操作
short  sem_flg; // 0,  IPC_NOWAIT,  SEM_UNDO
};

nops: 要操作的信号灯的个数

函数返回值

成功:0

出错:-1

为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。

举例

sem.h

#ifndef __SEM_H__
#define __SEM_H__
#if 0
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) */
};
#endif
int init_sem(int semid, int num, int val)
{
    union semun myun;
    myun.val = val;
    if(semctl(semid, num, SETVAL, myun) < 0)
    {
        perror("semctl");
        exit(1);
    }
    return 0;
}
int sem_p(int semid, int num)
{
    struct sembuf mybuf;
    mybuf.sem_num = num;
    mybuf.sem_op = -1;
    mybuf.sem_flg = SEM_UNDO;
    if(semop(semid, &mybuf, 1) < 0)
    {
        perror("semop");
        exit(1);
    }
    return 0;
}
int sem_v(int semid, int num)
{
    struct sembuf mybuf;
    mybuf.sem_num = num;
    mybuf.sem_op = 1;
    mybuf.sem_flg = SEM_UNDO;
    if(semop(semid, &mybuf, 1) < 0)
    {
        perror("semop");
        exit(1);
    }
    return 0;
}
#endif

client

#include <sys/types.h>
#include <linux/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "sem.h"
int main(void)
{
    key_t key;   //sem key
    int semid, semval;
    //get semaphore
    if((key = ftok("/app",'i')) <0)
    {
        perror("ftok");
        exit(1);
    }
    printf("key = %x\n",key);
    if((semid  = semget(key, 1, 0666)) < 0)
    {
        perror("semget");
        exit(1);
    }
    //get semaphore value every 1 seconds  
    while (1) 
    {
        if ((semval = semctl(semid, 0, GETVAL, 0)) == -1) 
        {
            perror("semctl error!\n");
            exit(1);
        }
        if (semval > 0) 
        {
            printf("Still %d resources can be used\n", semval);
        }else 
        {
            printf("No more resources can be used!\n");
            //break;
        }
        sleep(1);
    }
    exit(0);
}

server

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/sem.h>
#include <stdio.h>
#include <errno.h>
#include "sem.h"
#define MAX_RESOURCE    10
int main(void)
{
key_t key_info;
int semid;
//get semaphore 
if ((key_info = ftok ("/app", 'i')) < 0)
{
    perror ("ftok info");
    exit (-1);
}
printf("key is %d\n", key_info);
if ((semid = semget (key_info, 1, IPC_CREAT | IPC_EXCL |0666)) < 0)
{
    if (errno == EEXIST)
    {
        semid = semget (key_info, 1, 0666);
    }
    else
    {
        perror ("semget");
        exit (-1);
    }
}
else
{
    init_sem (semid, 0, 1);
}
//substract 1 every 3 seconds until semaphore value is -1
while (1) 
{
    //p
    printf("p\n");
    sem_p (semid, 0);
    //进入临界区
    sleep(4);
    //V
    printf("v\n");
    sem_v (semid, 0);
    sleep(3);
}
exit(0);
}

信号量与共享内存

信号

当我们按下ctrl+c终止主控程序时,希望能够实现如下操作:

1.进程退出

2.释放所有申请的资源

互斥锁、条件变量、消息队列、共享内存、信号量、线程、设备文件描述符(摄像头、串口)

信号通信

• 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

• 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。

• 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;

• 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程

信号的生存周期

用户进程对信号的响应方式:

• 忽略信号:• 对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。

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

• 执行缺省操作:• Linux对每种信号都规定了默认操作

信号处理流程

信号类型

  • SIGHUP

含义

该信号在用户终端连接(正常或非正常)结束时发出,

通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。

默认操作

终止

  • SIGINT

该信号在用户键入INTR字符(通常是Ctrl-C)时发出, 终端驱动程序发送此信号并送到前台进程中的每一个进程。

终止

  • SIGQUIT
    该信号和SIGINT类似,但由QUIT字符(通常是 Ctrl-)来控制。
    终止
  • SIGILL
    该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。
    终止
  • SIGFPE
    该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。
    终止
  • SIGKILL
    该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
    终止
  • SIGALRM
    该信号当一个定时器到时的时候发出。
    终止
  • SIGSTOP
    该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
    暂停进程
  • SIGTSTP
    该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。
    暂停进程
  • SIGCHLD
    子进程改变状态时,父进程会收到这个信号
    忽略
  • SIGABORT
    该信号用于结束进程
    终止

信号发送

kill()和raise()

• kill函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(实际上, kill系统命令只是kill函数的一个用户接口)。

• kill – l 命令查看系统支持的信号列表

• Raise 函数允许进程向自己发送信号

NAME
raise - send a signal to the caller
SYNOPSIS
#include <signal.h>
   int raise(int sig);
DESCRIPTION
       The raise() function sends a signal to the calling process or thread.  In a single-threaded program it is equivalent to
           kill(getpid(), sig);
       In a multithreaded program it is equivalent to
           pthread_kill(pthread_self(), sig);
       If the signal causes a handler to be called, raise() will return only after the signal handler has returned.

设置信号的处理方式

• 一个进程可以设定对信号的相应方式

• 信号处理的主要方法有两种

使用简单的signal()函数

使用信号集函数族sigaction

• signal()

使用signal函数处理时,需指定要处理的信号和处理函数

使用简单、易于理解

信号设置函数-signal

#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int); 
//void (*handler)(int)函数指针

signum:指定信号

handler:

SIG_IGN:忽略该信号。

SIG_DFL:采用系统默认方式处理信号。

自定义的信号处理函数指针

函数返回值

成功:设置之前的信号处理方式

出错:-1

举例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
void sig_handler(int signo);
int main(int argc, char *argv[])
{
    int time_left;
    #if 1
    if (signal(SIGINT, sig_handler) == SIG_ERR) {
        perror("signal error");
        return -1;
    }
    signal(SIGQUIT, sig_handler);    
    #endif
    printf("begin sleep ...\n");
    time_left = sleep(10);
    printf("end sleep ... time_left=%d\n", time_left);
    while(1);
    return 0;
}
void sig_handler(int signo)
{
    if (signo == SIGINT)
        printf("I received a SIGINT!\n");
  signal(SIGINT, SIG_DFL);
    if (signo == SIGQUIT)
        printf("I received a SIGQUIT!\n");    
}

进程间通讯方式比较

• signa(信号)l: 唯一的异步通信方式

• msg(消息队列):常用于cs模式中, 按消息类型访问,可有优先级

• shm(共享内存):效率最高(直接访问内存) ,需要同步、互斥机制

• sem(信号量):配合共享内存使用,用以实现同步和互斥

• pipe(管道): 具有亲缘关系的进程间,单工,数据在内存中

• fifo: 可用于任意进程间,双工,有文件名,数据在内存,mkfifo file

• Socket:不同主机之间进程通信

目录
相关文章
|
1月前
|
消息中间件 存储 供应链
进程间通信方式-----消息队列通信
【10月更文挑战第29天】消息队列通信是一种强大而灵活的进程间通信机制,它通过异步通信、解耦和缓冲等特性,为分布式系统和多进程应用提供了高效的通信方式。在实际应用中,需要根据具体的需求和场景,合理地选择和使用消息队列,以充分发挥其优势,同时注意其可能带来的复杂性和性能开销等问题。
|
3月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
3月前
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。
|
3月前
|
NoSQL
gdb中获取进程收到的最近一个信号的信息
gdb中获取进程收到的最近一个信号的信息
|
4月前
|
消息中间件 Linux 开发者
Linux进程间通信秘籍:管道、消息队列、信号量,一文让你彻底解锁!
【8月更文挑战第25天】本文概述了Linux系统中常用的五种进程间通信(IPC)模式:管道、消息队列、信号量、共享内存与套接字。通过示例代码展示了每种模式的应用场景。了解这些IPC机制及其特点有助于开发者根据具体需求选择合适的通信方式,促进多进程间的高效协作。
188 3
|
4月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
78 0
|
5月前
|
消息中间件 C语言 RocketMQ
消息队列 MQ操作报错合集之出现"Connection reset by peer"的错误,该如何处理
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
5月前
|
消息中间件 Java C语言
消息队列 MQ使用问题之在使用C++客户端和GBase的ESQL进行编译时出现core dump,该怎么办
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
1月前
|
消息中间件 存储 Kafka
MQ 消息队列核心原理,12 条最全面总结!
本文总结了消息队列的12个核心原理,涵盖消息顺序性、ACK机制、持久化及高可用性等内容。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
3月前
|
消息中间件
手撸MQ消息队列——循环数组
队列是一种常用的数据结构,类似于栈,但采用先进先出(FIFO)的原则。生活中常见的排队场景就是队列的应用实例。在数据结构中,队列通常用数组实现,包括入队(队尾插入元素)和出队(队头移除元素)两种基本操作。本文介绍了如何用数组实现队列,包括定义数组长度、维护队头和队尾下标(front 和 tail),并通过取模运算解决下标越界问题。此外,还讨论了队列的空与满状态判断,以及并发和等待机制的实现。通过示例代码展示了队列的基本操作及优化方法,确保多线程环境下的正确性和高效性。
53 0
手撸MQ消息队列——循环数组