• 首页
  • 搜索
  • 工具
  • 分类
  • 日志
  • 友链
  • 图片

It's Geek KingYoungy

KEEP CHALLENGE

标签 C 下的文章

多进程/多线程并发编程

POSIX进程间通信技术详解

2025-03-23 浏览量 142 暂无评论

POSIX IPC机制详解:消息队列、信号量与共享内存

在操作系统的多进程协作中,POSIX IPC(可移植操作系统接口进程间通信)提供了一套标准化的进程间通信机制,涵盖消息队列、信号量和共享内存三种核心方式。相较于传统的System V IPC,POSIX IPC具有更简洁的API设计和更高的跨平台兼容性。本文将深入解析这三种机制的系统调用及其实现细节。


一、消息队列(Message Queues)

消息队列是一种基于链表的异步通信机制,支持消息的优先级和非阻塞读写。

1. mq_open:创建或打开消息队列

#include <fcntl.h>
#include <mqueue.h>

mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

• 功能:创建或打开一个消息队列。
• 参数:
• name:队列名称,以/开头且不含其他/(如/my_queue)。
• oflag:标志位,常用组合:

◦ `O_RDONLY`:只读模式
◦ `O_WRONLY`:只写模式
◦ `O_RDWR`:读写模式
◦ `O_CREAT`:若不存在则创建
◦ `O_EXCL`:与`O_CREAT`联用,确保创建新队列
◦ `O_NONBLOCK`:非阻塞模式

• mode:权限位(如0666),控制队列的读/写权限。
• attr:指向mq_attr结构体的指针,定义队列属性:

struct mq_attr {
    long mq_flags;    // 标志(通常为0)
    long mq_maxmsg;   // 队列最大消息数
    long mq_msgsize;  // 单条消息最大长度
    long mq_curmsgs;  // 当前队列中的消息数(仅读)
};

• 返回值:成功返回消息队列描述符(mqd_t),失败返回-1。


2. mq_send / mq_receive:发送与接收消息

int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
int mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);

• 功能:向队列发送或从队列接收消息。
• 参数:
• mqdes:消息队列描述符。
• msg_ptr:消息缓冲区指针。
• msg_len:消息长度(需≤mq_msgsize)。
• msg_prio:消息优先级(0最低,MQ_PRIO_MAX由系统定义)。
• 返回值:成功返回0(mq_send)或接收的字节数(mq_receive),失败返回-1。


3. mq_close / mq_unlink:关闭与删除队列

int mq_close(mqd_t mqdes);
int mq_unlink(const char *name);

• 功能:关闭队列描述符或删除队列实体。
• 参数:
• mqdes:待关闭的描述符。
• name:待删除的队列名称。
• 返回值:成功返回0,失败返回-1。


二、信号量(Semaphores)

信号量用于进程间的同步控制,支持二进制和计数两种类型。

1. sem_open:创建或打开命名信号量

#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

• 功能:创建或打开一个命名信号量。
• 参数:
• name:信号量名称(格式同消息队列)。
• oflag:标志位(O_CREAT、O_EXCL等)。
• mode:权限位(如0666)。
• value:信号量初始值。
• 返回值:成功返回信号量指针,失败返回SEM_FAILED。


2. sem_init / sem_destroy:初始化与销毁匿名信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);

• 功能:初始化或销毁匿名信号量(仅限线程或进程内使用)。
• 参数:
• sem:信号量指针。
• pshared:共享标志(0表示线程间,非0表示进程间)。
• value:初始值。
• 返回值:成功返回0,失败返回-1。


3. sem_wait / sem_post:P/V操作

int sem_wait(sem_t *sem);     // P操作(阻塞直到信号量>0)
int sem_trywait(sem_t *sem);  // 非阻塞P操作
int sem_post(sem_t *sem);     // V操作(信号量+1)

• 功能:执行信号量的增减操作。
• 参数:sem:信号量指针。
• 返回值:成功返回0,失败返回-1。


三、共享内存(Shared Memory)

共享内存允许进程直接访问同一块物理内存,是IPC中最高效的方式。

1. shm_open:创建或打开共享内存对象

#include <sys/mman.h>
#include <sys/stat.h>

int shm_open(const char *name, int oflag, mode_t mode);

• 功能:创建或打开共享内存对象。
• 参数:
• name:对象名称(格式同消息队列)。
• oflag:标志位(O_CREAT、O_RDWR等)。
• mode:权限位(如0666)。
• 返回值:成功返回文件描述符,失败返回-1。


2. ftruncate:调整共享内存大小

int ftruncate(int fd, off_t length);

• 功能:设置共享内存对象的长度。
• 参数:
• fd:共享内存文件描述符。
• length:目标长度(字节)。
• 返回值:成功返回0,失败返回-1。


3. mmap / munmap:映射与解除映射

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

• 功能:将共享内存映射到进程地址空间或解除映射。
• 参数:
• addr:映射起始地址(通常设为NULL由系统选择)。
• length:映射长度。
• prot:保护模式:

◦ `PROT_READ`:可读
◦ `PROT_WRITE`:可写
◦ `PROT_EXEC`:可执行
◦ `PROT_NONE`:不可访问

• flags:映射标志:

◦ `MAP_SHARED`:多进程共享
◦ `MAP_PRIVATE`:私有映射(写时复制)

• fd:共享内存文件描述符。
• offset:文件偏移量(通常为0)。
• 返回值:成功返回映射地址指针(mmap)或0(munmap),失败返回MAP_FAILED或-1。


4. shm_unlink:删除共享内存对象

int shm_unlink(const char *name);

• 功能:删除共享内存对象。
• 参数:name:对象名称。
• 返回值:成功返回0,失败返回-1。


总结

POSIX IPC通过标准化的API设计,为进程间通信提供了高效、可靠的解决方案。消息队列适用于异步任务分发,信号量用于资源同步,共享内存则适合高频数据交换。开发时应根据场景需求选择合适机制,并注意资源释放与同步问题。

- 阅读全文 -
多进程/多线程并发编程

内存映射与内存锁详解

2025-03-21 浏览量 153 暂无评论

内存映射与内存锁详解


Linux内存映射相关系统调用详解

内存映射是Linux系统中高效管理内存和文件的核心机制之一。通过将文件或设备直接映射到进程的虚拟地址空间,程序可以像访问内存一样操作数据,避免了频繁的用户态与内核态切换。本文将详细介绍与内存映射相关的系统调用,包括其功能、参数及使用细节。


1. mmap():创建内存映射

功能:将文件或匿名内存区域映射到进程的虚拟地址空间,支持文件I/O(共享文件映射)、共享内存(共享匿名映射)、动态内存(私有匿名映射)分配等场景。

函数声明:

#include <sys/mman.h>  
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);  

参数详解:
• addr:建议映射的起始地址,通常设为NULL由内核自动分配。
• length:映射区域的长度,需按页大小对齐(通常4KB)。
• prot:内存保护标志,通过位或组合以下宏:
• PROT_READ:可读。
• PROT_WRITE:可写。
• PROT_EXEC:可执行。
• PROT_NONE:不可访问。
• flags:映射类型和属性,常用宏包括:
• MAP_PRIVATE:私有映射,修改仅对当前进程可见(写时复制),不同进程使用时各自copy一份,若是私有文件映射则不会被写入磁盘。
• MAP_SHARED:直接使用,无需copy,与私有映射二选一。
• MAP_ANONYMOUS:匿名映射,不关联文件,内容初始化为零,当然默认是文件映射。
• MAP_FIXED:强制使用指定地址,可能导致现有映射被覆盖。
• fd:文件描述符,匿名映射时设为-1。
• offset:文件映射的起始偏移量,需为页大小的整数倍。

返回值:
• 成功返回映射区域的起始地址。
• 失败返回MAP_FAILED,并设置errno(如EACCES权限不足、ENOMEM内存不足)。


2. munmap():解除内存映射

功能:删除指定虚拟地址范围内的内存映射,释放相关资源。

函数声明:

#include <sys/mman.h>  
int munmap(void *addr, size_t length);  

参数说明:
• addr:映射区域的起始地址,必须与mmap()返回的地址一致。
• length:需解除映射的区域长度,无需严格对齐,但内核按页对齐处理。

返回值:
• 成功返回0。
• 失败返回-1,并设置errno(如EINVAL无效地址或长度)。


3. mprotect():修改内存保护权限

功能:调整已映射内存区域的访问权限(如将只读区域改为可写)。

函数声明:

#include <sys/mman.h>  
int mprotect(void *addr, size_t len, int prot);  

参数说明:
• addr和len:需调整的地址范围,必须对齐页大小。
• prot:与mmap()的prot参数相同,支持PROT_READ、PROT_WRITE等组合。

返回值:
• 成功返回0。
• 失败返回-1,并设置errno(如EACCES权限冲突)。


4. msync():同步内存与文件内容

功能:将映射内存的修改同步到文件,确保数据持久化。

函数声明:

#include <sys/mman.h>  
int msync(void *addr, size_t length, int flags);  

参数说明:
• addr和length:需同步的内存区域。
• flags:同步模式,可选宏:
• MS_SYNC:同步写入磁盘,调用阻塞直到完成。
• MS_ASYNC:异步写入,仅将数据排队到写入队列。
• MS_INVALIDATE:使其他进程的映射缓存失效。

返回值:
• 成功返回0。
• 失败返回-1,并设置errno(如EIO写入错误)。


5. madvise():提供内存访问建议(高级优化)

功能:向内核提供内存访问模式的提示,以优化页面缓存和预读策略。

函数声明:

#include <sys/mman.h>  
int madvise(void *addr, size_t length, int advice);  

参数说明:
• addr和length:目标内存区域。
• advice:建议类型,常用选项包括:
• MADV_NORMAL:默认策略,适度预读。
• MADV_RANDOM:随机访问,禁用预读。
• MADV_SEQUENTIAL:顺序访问,积极释放已访问页。
• MADV_DONTNEED:提示内核可释放相关物理页。

返回值:
• 成功返回0。
• 失败返回-1,并设置errno。


总结

Linux内存映射系统调用提供了灵活的内存管理能力,涵盖从映射创建(mmap)、权限控制(mprotect)、数据同步(msync)到资源释放(munmap)的全生命周期操作。合理使用这些接口可显著提升文件I/O效率,简化进程间通信,并优化内存利用率。开发者需特别注意参数的对齐要求和标志位的组合逻辑,以避免潜在错误。


Linux内存锁相关系统调用详解

内存锁定(Memory Locking)是Linux系统中用于确保关键内存页常驻物理内存的核心机制,可避免被交换到磁盘,从而提升实时性、安全性及性能稳定性。以下是与内存锁相关的系统调用详解,包含功能、参数、宏定义及返回值的完整说明。


1. mlock():锁定指定内存区域

功能:将进程虚拟地址空间中指定范围的物理内存页锁定在物理内存中,防止被交换到交换分区。

函数声明:

#include <sys/mman.h>  
int mlock(const void *addr, size_t len);  

参数说明:
• addr:需锁定的内存区域起始地址,必须按页对齐(通常4KB)。
• len:需锁定的内存区域长度,需为页大小的整数倍。

返回值:
• 成功返回0。
• 失败返回-1,并设置errno,常见错误包括:
• ENOMEM:系统内存不足或地址范围超出进程地址空间。
• EPERM:进程无权限(非特权用户需CAP_IPC_LOCK能力)。
• EINVAL:参数非法(如len=0或地址未对齐)。


2. munlock():解除内存锁定

功能:解锁由mlock()锁定的内存区域,允许内核重新管理其换入换出。

函数声明:

#include <sys/mman.h>  
int munlock(const void *addr, size_t len);  

参数说明:
• addr和len:需解锁的内存区域,必须与mlock()调用时的范围完全一致。

返回值:
• 成功返回0。
• 失败返回-1,错误码与mlock()类似。


3. mlockall():锁定进程全部内存

功能:锁定当前或未来进程地址空间的所有内存页,常与实时性要求高的应用结合使用。

函数声明:

#include <sys/mman.h>  
int mlockall(int flags);  

参数说明:
• flags:锁定模式,通过位或组合以下宏:
• MCL_CURRENT:锁定当前已分配的所有内存页。
• MCL_FUTURE:锁定未来新分配的内存页(如堆、栈增长)。
• MCL_ONFAULT(Linux特有):仅锁定已触发缺页异常的页。

返回值:
• 成功返回0。
• 失败返回-1,错误码包括EPERM(权限不足)或ENOMEM(资源耗尽)。


4. munlockall():解除全部内存锁定

功能:解除由mlockall()锁定的所有内存页。

函数声明:

#include <sys/mman.h>  
int munlockall(void);  

返回值:
• 成功返回0。
• 失败返回-1,通常因权限问题(EPERM)。


5. mseal():密封内存权限(新增系统调用)

功能:锁定指定内存区域的权限(如禁止修改为可执行或调整映射范围),防止攻击者利用内存漏洞篡改权限。

函数声明:

#include <linux/mseal.h>  
int mseal(void *addr, size_t len, unsigned long flags);  

参数说明:
• addr和len:需密封的内存区域。
• flags:密封类型,常用宏包括:
• SEAL_PROT:禁止修改内存保护权限(如PROT_EXEC)。
• SEAL_MAP:禁止调整映射范围(如munmap或mremap)。
• SEAL_SHRINK:禁止缩小映射区域。

返回值:
• 成功返回0。
• 失败返回-1,错误码包括EACCES(权限不足)或EINVAL(参数无效)。


核心机制与限制

  1. 实现原理:
    • 内存锁定通过设置VMA(虚拟内存区域)的VM_LOCKED标记实现,内核在页面回收(Page Reclaim)时跳过锁定页。
    • 锁定页会被移出LRU链表,加入unevictable_list,避免被扫描回收。
  2. 资源限制:
    • 用户可通过ulimit -l查看和调整锁定内存上限,默认值通常为64KB。
    • 超限时mlock()返回ENOMEM。
  3. 安全性与性能:
    • 特权进程(CAP_IPC_LOCK)可绕过限制,但过度锁定可能导致系统资源耗尽。
    • mseal通过固化内存权限提升安全性,尤其适用于浏览器JIT编译等高风险场景。

总结

内存锁相关系统调用为关键应用提供了物理内存驻留和权限控制的底层支持。mlock/mlockall适用于实时性场景,mseal则针对新型内存攻击提供主动防御。开发者需权衡性能与资源消耗,结合ulimit和权限管理避免系统稳定性问题。

- 阅读全文 -
C/C++Unix/Linux应用层程序设计

Local聊天室

2025-03-16 浏览量 123 暂无评论
LOCAL聊天室_client.1.1.drawio.png

本项目基于SYSTEM V消息队列、POSIX线程、ncurses库进行实现,能够创建多个聊天室,用户加入聊天室即可与同一聊天室的其他所有用户下聊天。类似于wx与qq的群聊功能。
使用图:屏幕截图 2025-03-16 181259.png
流程图:LOCAL聊天室_client.1.1.drawio.png LOCAL聊天室_server.1.1.drawio.png
开发日志:
教训(花了我近乎两个小时):
共享库命名一定要规范:.so.x.y.z
不然ld链接器找不到
AI的能力不足以找到错误。

2025 3.15 又一个教训
客户端,服务器头文件不统一。服务器改了头文件的部分内容,客户端没有及时修改
花了我又快两个小时。
解决方法:今后项目根目录新建include文件夹存放头文件

开发的时候,在关键的步骤一定要用printf打印状态/要干什么事/干完了什么事
不可自负,相信能一遍做完大项目
若在开发前期写好输出到日志文件的函数,将会事半功倍,不用在大功告成时注释掉或删除掉密密麻麻的printf了
但这样的坏处就是不能边运行边看。

2025 3.16 最后两个教训
收包输出但是得到乱码不是因为别的,就是发的缓冲区与收的缓冲区大小没设置好,以为发送端会发那么多没想到那个发送大小宏定义竟然是0,最后导致内存访问出界了

本来想搞一个无限循环了的,手贱在一个if语句的最后加了一个break,还在想服务器服务线程怎么莫名其妙不吭声了,操原来是break了。
还在想有什么逻辑错误,没想到是这种小错误。

- 阅读全文 -
多进程/多线程并发编程

System V 消息队列总结

2025-03-05 浏览量 130 暂无评论

System V 消息队列总结:

前言

消息队列是一种面向消息的ipc机制,即内核会保证进程对消息队列IO时数据块(消息)的整体性。且其是双工的,任何进程都可以往里放消息,同时任何进程都可以往里读消息(甚至读到其自己的)。


1. msgget - 创建消息队列/获取已有消息队列的标识符

#include <sys/msg.h>

int msgget(key_t key, int msgflg);

• 参数:
• key:消息队列的唯一键值(通常由 ftok 生成),或使用 IPC_PRIVATE 创建私有队列。
• msgflg:标志位(如 IPC_CREAT 创建队列,IPC_EXCL 配合 IPC_CREAT 确保队列不存在时新建)。
• 返回值:成功返回消息队列标识符(msqid),失败返回 -1。


2. msgsnd - 发送消息

#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

• 参数:
• msqid:消息队列标识符。
• msgp:指向消息结构体的指针(需自定义,见下文)。
• msgsz:消息内容(mtext)的字节数(不包括 mtype 字段)。
• msgflg:标志位(如 IPC_NOWAIT 非阻塞发送,队列满时立即返回错误)。
• 返回值:成功返回 0,失败返回 -1。

注意:就算多进程/线程同时非阻塞地调用该函数,内核依旧会保证消息之间地独立性。因为内核并不是依靠其阻塞来保证独立性的。


3. msgrcv - 接收消息

#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

• 参数:
• msqid:消息队列标识符。
• msgp:接收消息的缓冲区指针(需自定义消息结构体)。
• msgsz:缓冲区中 mtext 部分的最大容量。
• msgtyp:指定接收消息的类型(见下文规则)。
• msgflg:标志位(如 IPC_NOWAIT 非阻塞接收,MSG_NOERROR 允许截断过长消息)。
• 返回值:成功返回实际接收的 mtext 字节数,失败返回 -1。


4. msgctl - 控制消息队列

#include <sys/msg.h>

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

• 参数:
• msqid:消息队列标识符。
• cmd:控制命令(常用值):

◦ **`IPC_STAT`**:获取队列元数据到 `buf`。
◦ **`IPC_SET`**:通过 `buf` 修改队列元数据(如权限、容量限制)。
◦ **`IPC_RMID`**:立即删除队列(`buf` 可设为 `NULL`)。

• buf:指向 struct msqid_ds 的指针(用于读写元数据)。
• 返回值:成功返回 0,失败返回 -1。


关键数据结构

消息结构体(需自定义)

struct msgbuf {
    long mtype;     // 消息类型(必须 > 0)
    char mtext[];  // 消息内容(实际长度由 `msgsz` 指定,这里的字符数组表示就是一块内存,可以是各种数据的组合)
};

队列元数据结构体(struct msqid_ds)

提取自内核维护的msg_ids数据结构的对应entries指针指向的结构。

struct msqid_ds {
    struct ipc_perm msg_perm;  // 权限信息(所有者、读写权限等)
    time_t          msg_stime; // 最后发送时间
    time_t          msg_rtime; // 最后接收时间
    time_t          msg_ctime; // 最后修改时间
    unsigned long   msg_cbytes; // 当前队列中的字节数
    msgqnum_t       msg_qnum;   // 当前队列中的消息数
    msglen_t        msg_qbytes; // 队列最大容量(字节数)
    pid_t           msg_lspid;  // 最后发送消息的进程PID
    pid_t           msg_lrpid;  // 最后接收消息的进程PID
};

核心原理

  1. 消息类型(mtype):
    • 用于分类消息,接收时可指定类型过滤(如 msgtyp > 0 匹配特定类型,msgtyp = 0 接收任意类型,msgtyp < 0 接受mtype小于msgtyp绝对值的mtype最小的消息)。
    • 类型为 long 类型,必须为正整数。
  2. 内核管理:
    • 消息队列以内核对象形式存在,独立于进程生命周期(需显式调用 msgctl(IPC_RMID) 删除)。
    • 消息以链表形式存储,保证先进先出(FIFO),但可通过 msgtyp 实现优先级读取。
  3. 同步与原子性:
    • msgsnd 和 msgrcv 是原子操作:单次调用发送或接收一条完整消息。
    • 若队列满或空,默认阻塞进程(除非指定 IPC_NOWAIT)。

示例代码片段

// 定义消息结构体(实际使用需动态分配 mtext 大小)
struct my_msg {
    long mtype;
    char mtext[100];
};

// 发送消息
struct my_msg msg_send = {1, "Hello"};
msgsnd(msqid, &msg_send, strlen(msg_send.mtext), 0);

// 接收消息
struct my_msg msg_recv;
msgrcv(msqid, &msg_recv, sizeof(msg_recv.mtext), 1, 0);

总结

• 适用场景:需要类型过滤、原子性操作或异步通信的进程间交互(如任务分发、事件通知)。
• 缺点:依赖内核资源,需手动管理队列生命周期;跨平台支持较弱(推荐优先考虑 POSIX 消息队列或套接字)。

- 阅读全文 -
  1. 1
  2. 2

浏览量 : 5665

© 2025 It's Geek KingYoungy. Power By Typecho . Theme by Shiyi

浙ICP备2025160639号  |  浙公网安备33020502001222号

This is just a placeholder img.