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

It's Geek KingYoungy

KEEP CHALLENGE
多进程/多线程并发编程

System V 信号量与共享内存技术解析

2025-03-19 浏览量 136 暂无评论

System V 信号量与共享内存技术解析


一、System V 信号量

System V 信号量与共享内存是 Linux 系统中用于进程间通信(IPC)的重要机制。以下是关于信号量创建部分的详细解析:

1. semctl - 信号量的创建

声明:

int semget(key_t key, int nsems, int semflg);

参数详解:

  1. key:
    信号量集的键值,通常通过 ftok 函数生成,用于唯一标识信号量集。可以使用 IPC_PRIVATE 来创建一个新的、唯一的信号量集。
  2. nsems:
    信号量集中信号量的数量。如果只是访问已存在的信号量集,可以将其设为 0。
  3. semflg:
    标志位,用于指定信号量集的创建和访问权限。常用的标志包括:
    • IPC_CREAT:如果信号量集不存在,则创建它。
    • IPC_EXCL:与 IPC_CREAT 一起使用,如果信号量集已存在,则返回错误。
    • 权限模式:如 0666,表示所有用户可读写。

返回值:
• 成功返回信号量集的标识符,失败返回 -1。

2. semctl - 控制信号量集(通常用于初始化或查看信号量集合状态)

声明:

int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);

参数详解:

  1. semid:
    信号量集的标识符,由 semget 返回。需确保进程对该信号量集有操作权限(如 IPC_RMID 需要所有者或超级用户权限)。
  2. semnum:
    信号量在集合中的索引(从 0 开始)。仅对单信号量操作命令(如 SETVAL、GETVAL)有效;若 cmd 作用于整个集合(如 IPC_RMID),此参数可设为 0。
  3. cmd:
    控制命令,决定操作类型及 arg 参数用途:
    • IPC_STAT:获取信号量集状态信息,arg 需指向 struct semid_ds 结构体。该结构包含内核维护的元数据:

    struct semid_ds {
        struct ipc_perm sem_perm;  // 权限与所有者信息
        time_t sem_otime;         // 最后一次 semop() 成功的时间(初始为0)
        time_t sem_ctime;         // 信号量集创建/修改时间
        struct sem *sem_base;     // 指向信号量数组
        unsigned short sem_nsems; // 信号量数量
    };

    关键字段:
    ◦ sem_otime:首次创建时初始化为0,仅通过成功执行的 semop() 更新为非零值
    ◦ sem_ctime:记录信号量集的创建或配置修改时间
    • IPC_SET:修改信号量集权限,需 struct semid_ds 指针更新 sem_perm 字段
    • IPC_RMID:立即删除信号量集
    • SETVAL/GETVAL:设置或获取单个信号量的值

  4. arg(共用体 union semun):
    需用户自定义的联合体,根据 cmd 选择成员:

    union semun {
        int val;                // SETVAL/GETVAL 的值
        struct semid_ds *buf;   // IPC_STAT/IPC_SET 的状态缓冲区
        unsigned short *array;  // GETALL/SETALL 的数组指针
    };

多进程竞争防护策略:
在信号量集创建与初始化过程中,需防范以下竞争场景:
进程A调用 semget 创建信号量后未完成初始化,进程B获取同一信号量并修改值,随后被进程A的初始化覆盖

解决方案:利用sem_otime 同步机制只让一个进程创建并初始化信号量集合
• 创建者进程:初始化后执行一次空操作(sem_op=0)的 semop(),强制更新 sem_otime 为非零值
• 其他进程:循环检查 sem_otime 是否为0,若为0则等待至其变为非零:

struct semid_ds ds;
union semun arg = {.buf = &ds};
do {
    semctl(semid, 0, IPC_STAT, arg);
    if (ds.sem_otime != 0) break;  // 确认初始化完成
    usleep(1000);  // 避免忙等待
} while (1);
3. semop - 执行原子操作(PV 操作)

声明:

int semop(int semid, struct sembuf *sops, size_t nsops);

参数:
• semid:信号量标识符。
• sops:指向 sembuf 结构数组的指针,每个结构定义一次操作:

struct sembuf {
    unsigned short sem_num;  // 信号量索引
    short sem_op;            // 操作值(负数:P 操作;正数:V 操作)
    short sem_flg;           // 标志(如 `IPC_NOWAIT` 非阻塞,`SEM_UNDO` 防死锁)
};

• nsops:操作数组的长度。

返回值:
• 成功返回 0,失败返回 -1。


二、System V 共享内存

共享内存允许多进程直接读写同一物理内存区域,无需内核介入,是最高效的 IPC 方式。核心系统调用如下:

1. shmget - 创建/获取共享内存段

声明:

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

参数:
• key:共享内存标识键,规则同信号量。
• size:共享内存大小(单位:字节),实际分配按页对齐。
• shmflg:标志位,如 IPC_CREAT、IPC_EXCL 及权限模式。

返回值:
• 成功返回共享内存标识符,失败返回 -1。

生命周期管理:
共享内存段的生命周期由内核维护,即使所有进程解除映射,仍需显式调用 shmctl 的 IPC_RMID 命令删除。


2. shmat - 映射共享内存到进程地址空间

声明:

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:
• shmid:共享内存标识符。
• shmaddr:建议映射地址(通常设为 NULL,由系统自动分配)。
• shmflg:标志位,如 SHM_RDONLY(只读映射)。

返回值:
• 成功返回映射后的虚拟地址指针,失败返回 (void*) -1。

注意点:
• 共享内存在每个使用它的进程的附加位置(可以理解为挂载点)不同,因此若共享内存中存储了指针,若希望存储(也只能指向)共享内存的另一处位置,该指针不能存储绝对地址,而是只能存储到目标位置的偏移量。


3. shmdt - 解除映射

声明:

int shmdt(const void *shmaddr);

参数:
• shmaddr:shmat 返回的地址指针。

返回值:
• 成功返回 0,失败返回 -1。


4. shmctl - 控制共享内存段

声明:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:
• shmid:共享内存标识符。
• cmd:控制命令:
• IPC_STAT:获取状态信息到 buf。
• IPC_SET:修改权限或所有者。
• IPC_RMID:标记删除(当所有进程解除映射后实际释放)。
• buf:指向 shmid_ds 结构体的指针,用于存储或修改状态。

返回值:
• 成功返回 0,失败返回 -1。


三、关键注意事项与优化策略

  1. 同步机制选择:
    • 信号量类型:二元信号量(互斥锁)适用于独占资源访问;多元信号量(如连接池)可控制资源数量。
    • 性能排序:互斥锁 > POSIX 信号量 > System V 信号量 > 文件锁。
  2. 资源泄漏防护:
    • 显式调用 semctl/shmctl 删除 IPC 对象,避免因进程崩溃导致资源残留。
    • 使用 ipcs 和 ipcrm 命令检查与清理未释放的资源。
  3. 错误处理与调试:
    • 所有系统调用需检查返回值,捕获 EACCES(权限不足)、EINVAL(无效参数)等错误。
    • 通过 semctl 的 GETNCNT/GETZCNT 监控信号量等待队列,优化锁粒度。

四、总结

通过合理组合 System V 信号量与共享内存,可实现高效且安全的进程间协作。信号量的原子操作与共享内存的直接内存访问特性,使其在数据库连接池、实时数据处理等高并发场景中表现卓越。开发者需重点关注多进程初始化的竞争防护、资源生命周期管理及错误处理机制,以确保系统稳定性和性能。

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

Local聊天室

2025-03-16 浏览量 118 暂无评论
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了。
还在想有什么逻辑错误,没想到是这种小错误。

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

深入理解pthread互斥量与条件变量的使用

2025-03-12 浏览量 141 暂无评论

深入理解pthread互斥量与条件变量的使用

在多线程编程中,线程同步是一个关键问题。为了避免多个线程同时访问共享资源导致的数据竞争和不一致,POSIX线程库(pthread)提供了互斥量(Mutex)和条件变量(Condition Variable)两种重要的同步机制。本文将详细介绍它们的使用方法,并通过代码示例帮助读者更好地理解。

1. 互斥量(Mutex)

互斥量是一种简单的同步机制,用于保护共享资源,确保同一时间只有一个线程可以访问该资源。互斥量的基本操作包括初始化、加锁、解锁和销毁。

1.1 互斥量的初始化

互斥量可以通过静态或动态方式初始化。静态初始化使用宏PTHREAD_MUTEX_INITIALIZER,而动态初始化使用pthread_mutex_init函数。

// 静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 动态初始化
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
1.2 互斥量的加锁与解锁

线程在访问共享资源前需要加锁,访问完成后解锁。如果互斥量已被其他线程锁定,当前线程会被阻塞,直到互斥量被解锁。

pthread_mutex_lock(&mutex);
// 访问共享资源
pthread_mutex_unlock(&mutex);
1.3 互斥量的销毁

当互斥量不再需要时,应调用pthread_mutex_destroy函数销毁它,以释放系统资源。

pthread_mutex_destroy(&mutex);

2. 条件变量(Condition Variable)

条件变量用于生产者-消费者模型中,防止消费者拿到互斥量后的忙等待。它允许线程在某个条件未满足时进入等待状态并释放互斥锁,并在条件满足时被唤醒并请求互斥锁。条件变量通常与互斥量一起使用。

2.1 条件变量的初始化

条件变量也可以通过静态或动态方式初始化。

// 静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 动态初始化
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
2.2 条件变量的等待与通知

线程在等待条件变量时,会释放持有的互斥量,并在条件满足时重新获得互斥量。其他线程可以通过pthread_cond_signal或pthread_cond_broadcast唤醒等待的线程。

// 等待条件变量
pthread_mutex_lock(&mutex);
while (condition == false) { //一定要将循环检查共享资源,防止被唤醒后共享资源被没有被生产者生产
    pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);

// 通知条件变量
pthread_mutex_lock(&mutex);
condition = true;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
2.3 条件变量的销毁

当条件变量不再需要时,应调用pthread_cond_destroy函数销毁它。

pthread_cond_destroy(&cond);

3. 示例代码

以下是一个使用互斥量和条件变量的简单示例,展示了如何实现线程间的同步。

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;

void *thread_function(void *arg) {
    pthread_mutex_lock(&mutex);
    while (ready == 0) {
        pthread_cond_wait(&cond, &mutex);
    }
    printf("Thread condition met, proceeding\n");
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, thread_function, NULL);

    pthread_mutex_lock(&mutex);
    printf("Main thread signaling condition variable\n");
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);

    pthread_join(thread_id, NULL);
    return 0;
}

在这个示例中,主线程通过条件变量唤醒等待的线程,确保线程在条件满足时继续执行。

4. 总结

互斥量和条件变量是多线程编程中常用的同步机制。互斥量用于保护共享资源,而条件变量用于防止线程的忙等待,协调生产者与消费者线程。通过合理使用这两种机制,可以有效地避免数据竞争和死锁问题,提高多线程程序的稳定性和性能。

希望本文能帮助你更好地理解和使用pthread互斥量与条件变量。如果你有任何问题或需要进一步的帮助,请随时留言!

- 阅读全文 -

ncurses库的使用总结

2025-03-06 浏览量 113 暂无评论

ncurses库的使用总结


前言

ncurses(new curses)是一个程序库,它提供的API可以允许程序员编写独立于终端的基于文本的用户界面。它是一个虚拟终端中的“类GUI”应用软件工具箱。它还优化了屏幕刷新方法,以减少使用远程shell时遇到的延迟。


一、环境初始化函数

  1. WINDOW *initscr(void)
    • 参数:无
    • 返回值:返回标准窗口stdscr的指针;失败返回NULL。
    • 作用:初始化终端进入curses模式,必须在其他ncurses函数前调用。
    • 使用注意:每个程序只能调用一次,需与endwin()配对。
  2. int endwin(void)
    • 参数:无
    • 返回值:成功返回OK,失败返回ERR。
    • 作用:释放ncurses资源,恢复终端原始状态。需在程序退出前调用。

    // 示例:初始化与退出
    initscr();
    // ...其他操作...
    endwin();
  3. 信号处理(非函数原型,但关键)
    • 使用场景:防止程序异常终止导致终端显示异常。
    • 示例:

    #include <signal.h>
    void sig_handler(int signo) { endwin(); exit(0); }
    signal(SIGINT, sig_handler);  // 处理Ctrl+C

二、输入模式控制

  1. int raw(void) / int cbreak(void)
    • 参数:无
    • 返回值:成功返回OK,失败返回ERR。
    • 区别:
    ◦ raw():禁用行缓冲和信号处理(如Ctrl+C会直接传递为输入字符)。
    ◦ cbreak():禁用行缓冲但保留信号处理。
  2. int echo(void) / int noecho(void)
    • 参数:无
    • 作用:控制输入回显,noecho()常用于密码输入等场景。
  3. int keypad(WINDOW *win, bool bf)
    • 参数:
    ◦ win:目标窗口(通常为stdscr)。
    ◦ bf:TRUE启用功能键(如方向键/F1-F12),FALSE禁用。
    • 返回值:成功返回OK,失败返回ERR。
  4. int halfdelay(int tenths)
    • 参数:tenths为等待输入的0.1秒倍数(1-255)。
    • 作用:设置输入超时,超时后返回ERR。

三、屏幕输出与光标控制

  1. 基础输出函数
    • int addch(chtype ch)
    ◦ 参数:ch为字符(可组合属性,如ch | A_BOLD)。
    • int printw(const char *fmt, ...)
    ◦ 参数:类似printf,支持格式化字符串。
    • int addstr(const char *str)
    ◦ 参数:直接输出完整字符串。
  2. 光标移动函数
    • int move(int y, int x)
    ◦ 参数:目标坐标(y, x)(行、列)。
    ◦ 返回值:成功返回OK,失败返回ERR。
    • mvaddch(int y, int x, chtype ch)
    ◦ 等效操作:move(y, x); addch(ch);。
  3. 屏幕刷新与清空
    • int refresh(void):将stdscr的内容刷新到物理屏幕。
    • int erase(void):清空stdscr内容,不重置光标。
    • int clear(void):清屏并重置光标到(0,0)。

四、窗口管理

  1. 窗口创建与销毁
    • WINDOW *newwin(int nlines, int ncols, int y, int x)
    ◦ 参数:窗口行数、列数、起始坐标(y, x)。
    ◦ 返回值:新窗口指针。
    • int delwin(WINDOW *win)
    ◦ 注意:需先销毁子窗口再销毁父窗口。
  2. 窗口操作
    • int wrefresh(WINDOW *win):刷新指定窗口到屏幕。
    • int box(WINDOW *win, chtype verch, chtype horch)
    ◦ 参数:verch为垂直边框字符,horch为水平边框字符。

五、输入处理

  1. 字符输入
    • int getch(void)
    ◦ 返回值:阻塞模式下等待输入,返回字符或功能键宏(如KEY_LEFT)。
    • int nodelay(WINDOW *win, bool bf)
    ◦ 参数:bf为TRUE时进入非阻塞模式(无输入返回ERR)。
  2. 坐标获取宏
    • getyx(WINDOW *win, int y, int x)
    ◦ 作用:获取窗口当前光标坐标(y和x需为变量地址)。
    • getmaxyx(WINDOW *win, int y, int x)
    ◦ 作用:获取窗口最大行数和列数。

六、最佳实践示例

#include <ncurses.h>
#include <signal.h>

void sig_handler(int signo) { endwin(); exit(0); }

int main() {
    initscr();
    signal(SIGINT, sig_handler);
    cbreak();
    noecho();
    keypad(stdscr, TRUE);

    printw("Press F1 to exit");
    int ch = getch();
    if (ch == KEY_F(1)) {
        endwin();
        return 0;
    }

    endwin();
}

以上内容综合了多个文档来源,具体函数细节可参考官方手册或相关示例。

- 阅读全文 -
  1. 1
  2. ...
  3. 6
  4. 7
  5. 8
  6. 9
  7. 10
  • 站点概览
author

38 日志
7 分类
Creative Commons
  • 热门文章
  • 热评文章
  • 随机文章
  • 在 Debian 服务器上部署 FileBrowser 并集成到现有博客路径
  • 高等数学重要定义整理
  • 高等数学重要定理总结
  • C语言原子量的使用
  • 库、链接与执行
  • 欢迎使用 Typecho
  • 对底层IO的深度总结
  • 数据结构——树
  • 库、链接与执行
  • shell作业控制的两个问题:组长叛变与SIGHUP信号
  • C语言可变参数与命令行参数解析:stdarg与getopt详解
  • 使用GDB调试程序的完整指南
  • 终端编程详解
  • 高等数学刷题心得
  • DNS(IP)与服务名(端口)解析

浏览量 : 5443

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

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

This is just a placeholder img.