Linux系统编程-信号&进程间通信

目录

异步(Asynchronous)

信号

数据结构

1.kill

2.alarm

3.pause

4.setitimer

5.abort

信号集(sigset_t类型)

1.sigemptyset

2.sigfillset

3.sigaddset

4.sigdelset

5.sigismember

信号屏蔽

1.sigprocmask

2.sigpending

3.sigsuspend

[4. sigaction](#4. sigaction)

管道

匿名管道

命名管道

进程间通信

[Message Queues 消息队列](#Message Queues 消息队列)

1.msgget

2.msgsnd

3.msgrcv

4.msgctl

[Semaphore Arrays 信号量数组](#Semaphore Arrays 信号量数组)

1.semget

2.semop

3.semtimeop

4.semctl

[Shared Memory Segments 共享内存段](#Shared Memory Segments 共享内存段)

1.shmget

2.shmat

3.shmdt

4.shmctl

用信号量实现令牌桶

mytbf.h

mytbf.c

main.c

信号量的PV操作实例

sem_pv.c


异步(Asynchronous)

定义:异步操作是指在执行一个任务时,调用者不需要等待任务完成,可以继续执行其他操作。

特点:

非阻塞:调用者在任务执行期间不会被阻塞,可以继续执行其他任务。

并发执行:多个任务可以并发执行,提高资源利用率。

复杂性:由于任务是并发执行的,可能涉及到并发控制和状态管理,逻辑相对复杂。

处理方法:查询法,通知法


信号

信号是一种*异步*事件通知机制。它们用于通知进程发生了某些事件,从而允许进程对这些事件做出响应。信号机制是进程间通信(IPC)和进程控制的重要手段之一。信号是软件中断,信号的响应(应用层)依赖于中断(物理层)。

信号的基本概念

异步通知:信号是异步发送的,即发送信号的进程不需要等待信号被接收和处理。

软件中断:信号类似于软件中断,它们可以打断进程的正常执行流程,依赖于系统中断的实现。

有限的信号类型:系统定义了一组有限的信号类型,每个信号类型对应一个特定的事件。

默认行为:每个信号类型都有一个默认行为,例如终止进程、忽略信号等。

可处理:进程可以通过定义信号处理函数来改变信号的默认行为。

信号处理机制

信号处理函数:进程可以定义一个函数来处理接收到的信号。这个函数被称为信号处理函数。

信号掩码:进程可以通过设置信号掩码来暂时屏蔽某些信号,防止在关键操作期间被打断。

信号队列:信号会被放入操作系统内核中的信号队列中,进程需要定期检查信号队列并处理信号。

默认行为:如果进程没有定义信号处理函数,信号将执行默认行为。

信号的局限性

原子性问题:信号处理函数的执行可能会与进程的其他部分发生竞争条件。

资源限制:信号处理函数不能进行复杂的操作,因为它们可能会被频繁地调用。

栈限制:信号处理函数通常在单独的信号栈上执行,这限制了它们的可用空间。

行为不可靠:信号处理是内核进行,假如有快速重复调用情况下,行为不可靠

信号的响应过程^[3]:

信号从受到到响应有一个不可避免的延迟,级别相近的标准信号响应没有严格顺序。信号掩码mask和信号挂起集pending存储在操作系统内核中 ,但与每个进程的上下文相关联,操作系统内核通过进程控制块(PCB)管理这些信息,并在进程执行过程中根据信号掩码决定信号的传递和处理。信号的两个32位的位图mask(初始值默认全为1)和pending(初始值默认全为0),进程在执行过程中,会定期检查是否有信号需要处理,或者发生中断后,进程被放入等待列表。线程从内核态切换到用户态时,内核会检查maks&pending的值,当结果为0是表示没有信号发生(默认1&0为0),这就是为什么标准信号被忽略了。

1.当信号发生时,内核会在进程的信号 pending 位图中对应的位设置为 1,表示该信号已经到达但尚未处理。线程在由内核态切换为用户态^[1]时,会检查信号屏蔽字(mask)和 pending 位图。如果 mask 和 pending 的按位与(&)操作结果为 1,表示信号未被屏蔽且已到达,内核会将控制权转交给信号处理函数。

2.信号处理函数执行完毕后,内核会清除信号 pending 位图中对应的位,即将其置为 0,表示该信号已经被处理。信号屏蔽字(mask)通常不会在信号处理后被置 0,而是保持不变或根据信号处理函数中的设置进行更改。这是为了防止同一信号在处理函数执行期间再次被处理,除非显式地更改了屏蔽字。恢复执行:

3.信号处理函数返回后,内核会恢复线程的原始状态,包括程序计数器和寄存器状态,使得线程可以继续从接收到信号前的地方执行。线程的信号屏蔽字(mask)会根据需要进行调整,这通常在信号处理函数内部完成

[1]:进程从用户态切换到内核态时,内核会将当前进程的寄存器状态保存到其内核栈^[2]中,并将进程的状态信息保存在进程控制块(PCB)中。

[2]:内核栈是操作系统为每个进程在内核态时使用的栈。当进程从用户态切换到内核态时(例如执行系统调用或处理中断时),内核会使用这个栈来保存和恢复进程的状态。内核栈栈顶通常在进程控制块(PCB)中而是esp有所记录。

[3]:在了解过多线程后,我们知道操作系统其实是以最小线程为单位的,并且线程进程不分家,实际上进程只用pending位图,而他的线程都拥有独立的mask和pending,实际上是进程的线程在工作

数据结构

cpp 复制代码
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
->
void(*signal(int signum, void (*handler)(int)))
//将信号 signum 的处理方式设置为 handler,其中 handler 可以是 SIG_IGN、SIG_DFL 或一个程序员定义的函数地址(即"信号处理函数")。信号会打断堵塞的系统调用(open,write)

1.kill

cpp 复制代码
int kill(pid_t pid, int sig);

向进程发送信号,成功返回0,失败返回-1。

如果 pid 是正数,则向由 pid 指定的进程发送信号 sig。

如果 pid 等于 0,则向调用进程的进程组中的每个进程发送信号 sig。

如果 pid 等于 -1,则向调用进程有权限发送信号的每个进程发送信号 sig,除了进程 1(init)。

如果 pid 小于 -1,则向进程组发送信号 sig,该进程组的 ID 是 -pid。

如果 sig 是 0,则不发送信号,但仍然执行存在和权限检查;这可以用来检查调用者被允许发送信号的进程 ID 或进程组 ID 的存在。失败情况下要去看ERROR,是查询是本,没有权限,还是不存在。

2.alarm

cpp 复制代码
unsigned int alarm(unsigned int seconds);

安排在seconds秒后向调用进程发送SIGALRM信号,如果没有接收行为,SIGALRM信号默认终止程序(进程异常中止)。如果seconds为零,它会取消任何之前设置的定时器,并且不会发送 SIGALRM 信号。函数返回之前安排的警报还有多少秒将被发送,如果没有之前安排的警报,则返回零。

3.pause

cpp 复制代码
int pause(void);

使调用进程(或线程)休眠,直到接收到一个信号,该信号要么终止进程,要么导致信号捕获函数的调用。

4.setitimer

cpp 复制代码
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);


struct itimerval {
    struct timeval it_interval; /* 计时器的初始值 */
    struct timeval it_value;    /* 计时器的间隔值 */
};

struct timeval {
     time_t      tv_sec;   /* 秒 */
     suseconds_t tv_usec;  /* 微秒 */
};

使用 setitimer 函数可以设置计时器,当计时器到期时,会发送相应的信号。这可以用于实现定时任务或监控某些事件。成功时返回 0。失败时返回 -1,并设置 errno 以指示错误类型。

which:指定计时器的类型。可以是以下之一:

ITIMER_REAL:实时计时器,计时器到期时发送 SIGALRM 信号。

ITIMER_VIRTUAL:虚拟计时器,计时器到期时发送 SIGVTALRM 信号。

ITIMER_PROF:统计计时器,计时器到期时发送 SIGPROF 信号。

new_value:指向 itimerval 结构的指针,定义了计时器的新值。itimerval 结构包含两个 timeval 结构,分别表示初始值和间隔值。

old_value:指向 itimerval 结构的指针,用于存储计时器的旧值。

5.abort

cpp 复制代码
void abort(void);

该函数首先解除对SIGABRT信号的阻塞,然后为调用进程引发该信号,结束进程并且产生calldown文件


信号集(sigset_t类型)

1.sigemptyset

cpp 复制代码
int sigemptyset(sigset_t *set);

把信号集set清空

2.sigfillset

cpp 复制代码
int sigfillset(sigset_t *set);

把信号集set装满,包括所有信号

3.sigaddset

cpp 复制代码
int sigaddset(sigset_t *set, int signum);

把signum加入信号集set

4.sigdelset

cpp 复制代码
int sigdelset(sigset_t *set, int signum);

把signum从信号集set删除

5.sigismember

cpp 复制代码
int sigismember(const sigset_t *set, int signum);

判断signum是否信号集set的成员


信号屏蔽

1.sigprocmask

cpp 复制代码
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

这个函数允许进程改变其信号屏蔽字,即决定哪些信号在传递给进程之前会被系统自动阻塞。人为设置mask来影响信号扫描时候响应,how是宏SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK,sigset_t是信号集

2.sigpending

cpp 复制代码
int sigpending(sigset_t *set);

访问内核,输出信号集set的pending状态,因为这是用户态指令,而pending在内核态转用户态已经转变,因此得到的pending是之前的pending

3.sigsuspend

cpp 复制代码
int sigsuspend(const sigset_t *mask);

解除一个信号集mask的堵塞状态,然后马上进入等待信号的状态,这个操作是原子的

4. sigaction

cpp 复制代码
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);


struct sigaction {
            void     (*sa_handler)(int); //信号处理函数的指针。可以设置为 SIG_DFL(默认处理)、SIG_IGN(忽略信号)或者指向自定义的信号处理函数。
            void     (*sa_sigaction)(int, siginfo_t *, void *); //可以用suguinfo_t结构体(一些系统是共用体)获取信号的信息,以此进行对型号的更进一步处理
            sigset_t   sa_mask;  //信号集,定义了在信号处理函数执行期间需要屏蔽的信号。
            int        sa_flags; //标志位,用于设置信号处理的一些选项,如 SA_RESTART(使系统调用在信号处理后自动重启),SA_NOCHLDWAIT(子进程创建后启用自行消亡,无需收尸,防止僵尸进程)。
            void     (*sa_restorer)(void);
};

siginfo_t {
               int      si_signo;     /* 信号编号 */
               int      si_errno;     /* 一个错误号 */
               int      si_code;      /* 信号代码(宏值) */
               int      si_trapno;    /* 导致硬件生成信号的陷阱编号
                                         (在大多数架构中未使用) */
               pid_t    si_pid;       /* 发送进程的ID */
               uid_t    si_uid;       /* 发送进程的真实用户ID */
               int      si_status;    /* 退出值或信号 */
               clock_t  si_utime;     /* 消耗的用户时间 */
               clock_t  si_stime;     /* 消耗的系统时间 */
               union sigval si_value; /* 信号值 */
               int      si_int;       /* POSIX.1b 信号 */
               void    *si_ptr;       /* POSIX.1b 信号 */
               int      si_overrun;   /* 定时器溢出计数;
                                         POSIX.1b 定时器 */
               int      si_timerid;   /* 定时器ID;POSIX.1b 定时器 */
               void    *si_addr;      /* 导致故障的内存位置 */
               long     si_band;      /* 带事件(在 glibc 2.3.2 及更早版本中为 int) */
               int      si_fd;        /* 文件描述符 */
               short    si_addr_lsb;  /* 地址的最低有效位
                                         (自 Linux 2.6.32 起) */
               void    *si_lower;     /* 地址违规发生时的下限
                                         (自 Linux 3.19 起) */
               void    *si_upper;     /* 地址违规发生时的上限
                                         (自 Linux 3.19 起) */
               int      si_pkey;      /* 导致故障的 PTE 上的保护键
                                         (自 Linux 4.6 起) */
               void    *si_call_addr; /* 系统调用指令的地址
                                         (自 Linux 3.5 起) */
               int      si_syscall;   /* 尝试的系统调用编号
                                         (自 Linux 3.5 起) */
               unsigned int si_arch;  /* 尝试的系统调用的架构 */
    }

为进程设置或获取信号处理的详细信息,signum指定要设置或获取的信号的宏,act和oldact是sigaction结构体,其中的sa_mask可以设置屏蔽的信号集。成功返回0,失败返回-1.


管道

匿名管道

允许两个进程之间进行单向通信.没有文件系统中的对应实体,它不是文件系统中的一个文件,也没有与之关联的文件名。这种管道是临时的,仅存在于创建它的进程和与之通信的进程之间。一旦这些进程结束,管道也就随之消失,不会留下任何痕迹。

cpp 复制代码
int pipe(int pipefd[2]);

pipe创造一个匿名管道,可以实现进程(父子进程)间的通信.pipefd 是一个包含两个整数的数组,这两个整数分别代表管道的两个端点:pipefd[0] 是管道的读端。pipefd[1] 是管道的写端。成功0发生错误返回-1,并设置全局变量 errno 以指示错误类型

命名管道

通常称为FIFO(First In, First Out,先进先出)或命名管道文件,是一种特殊的文件类型,它允许进程通过一个文件名进行通信。与传统的管道不同,命名管道可以在不相关的进程之间进行通信,甚至可以跨网络使用。

cpp 复制代码
int mkfifo(const char *pathname, mode_t mode);

创建一个命名管道.pathname:这是一个指向以null结尾的字符串的指针,指定了要创建的命名管道的路径和文件名。mode:这是一个模式位掩码,定义了文件的权限。


进程间通信

XSI -> SysV

IPC -> Inter-Process Communication 进程间通信,shell指令ipcs可以看到IPC详情

key是IPC的关键,消息队列,共享内存段和信号量数组都接受key来产生自身详情(id等),并且用key的唯一性进行进程间通信

cpp 复制代码
key_t ftok(const char *pathname, int proj_id);

生成一个唯一的键(key)。这个键可以用于创建或访问一个消息队列或共享内存段,确保这些资源在进程间通信(IPC)中的唯一性.pathname是一个指向以null结尾的字符串的指针,指定了一个文件的路径名,函数将基于这个文件的inode信息和项目标识符proj_id来生成键。proj_id是一个整数,用于进一步确保生成的键的唯一性。它可以是任意值,但通常选择一个非负整数。

  • 函数的命名方式类似

  • xxxget 获取

  • xxxop 操作

  • xxxctl 控制


Message Queues 消息队列

1.msgget

cpp 复制代码
int msgget(key_t key, int msgflg);

获取消息队列标识符,创建或获取一个消息队列

2.msgsnd

cpp 复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

向指定的消息队列发送消息

3.msgrcv

cpp 复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

从指定的消息队列接收消息

4.msgctl

cpp 复制代码
int msgctl(int msqid, int op, struct msqid_ds *buf);

控制消息队列,可以用于设置消息队列属性或删除消息队列


Semaphore Arrays 信号量数组

信号量:是一种*同步*通信机制,信号量值是一个整数值,通常用于表示可用资源的数量。在互斥锁的情况下,信号量的值通常初始化为1,表示有一个资源可用。

P 操作(Proberen,测试)

-当一个进程需要访问共享资源时,它会执行 P 操作。

-如果信号量的值大于0,P 操作会将信号量的值减1,进程可以继续执行。

-如果信号量的值等于0,进程将被操作系统挂起(阻塞),直到信号量的值变为大于0。

V 操作(Verhogen,增加)

-当进程完成对共享资源的访问时,它会执行 V 操作。

-V 操作会将信号量的值增加1。

-如果有其他进程因为信号量的值为0而被阻塞,增加信号量的值可能会唤醒这些进程中的一个,使其可以继续执行。

1.semget

cpp 复制代码
int semget(key_t key, int nsems, int semflg);

创建或获取一个信号量集的标识符

2.semop

cpp 复制代码
int semop(int semid, struct sembuf *sops, size_t nsops);

对信号量集执行操作(如P操作或V操作),sops是结构图指针初始位置,nops是成员,结果体元素如下

-unsigned short sem_num; /* semaphore number */

-short sem_op; /* semaphore operation */

-short sem_flg; /* operation flags */

3.semtimeop

cpp 复制代码
int semtimedop(int semid, struct sembuf *sops, size_t nsops, const struct timespec *_Nullable timeout);

对信号量集执行操作,并可指定超时时间

4.semctl

cpp 复制代码
int semctl(int semid, int semnum, int op, ...);

控制信号量,可以用于设置信号量的值(初始化)、获取信号量的状态或删除信号量


Shared Memory Segments 共享内存段

1.shmget

cpp 复制代码
int shmget(key_t key, size_t size, int shmflg);

创建或获取一个共享内存标识符,其中key用于指定共享内存的唯一性,size是共享内存段的大小,shmflg是控制标志。

2.shmat

cpp 复制代码
void *shmat(int shmid, const void *_Nullable shmaddr, int shmflg);

将共享内存段附加到进程地址空间,shmid是共享内存的标识符,shmaddr是指定的附加地址(可选),shmflg是控制标志。

3.shmdt

cpp 复制代码
int shmdt(const void *shmaddr);

从进程地址空间分离共享内存段,shmaddr是共享内存段的地址。

4.shmctl

cpp 复制代码
int shmctl(int shmid, int op, struct shmid_ds *buf);

控制共享内存段,可以用于设置共享内存的状态、获取信息或删除共享内存,op是操作类型,buf是操作相关的数据结构。


用信号量实现令牌桶

mytbf.h

cpp 复制代码
#ifndef MYTBF_H__
#define MYTBF_H__

#define MYTBF_MAX 1024

typedef void mytbf_t;

mytbf_t *mytbf_init(int cps,int burst);

//获取token
int mytbf_fetchtoken(mytbf_t *,int);
//归还token
int mytbf_returntoken(mytbf_t *,int);

int mytbf_destroy(mytbf_t *);

#endif

mytbf.c

cpp 复制代码
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>

#include "mytbf.h"

struct mytbf_st{
    int csp;
    int burst;
    int token;
    int pos;//任务列表的下标
};

static struct mytbf_st *job[MYTBF_MAX];
static volatile int inited = 0;
static struct sigaction old_sa;
static struct itimerval old_itv;

static int get_free_pos(){
    for (int i = 0;i < MYTBF_MAX;i++){
        if (job[i] == NULL)
          return  i;
    }
    return -1;
}


//信号处理函数
static void handler(int sig,siginfo_t *infop,void *unused){
    struct itimerval itv;

    if (infop->si_code != SI_KERNEL){
        return ;
    }

    itv.it_interval.tv_sec = 1;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 1;
    itv.it_value.tv_usec = 0;
    if(setitimer(ITIMER_REAL,&itv,NULL) < 0){
        perror("setitimer()");
        exit(1);
    }
    for (int i = 0;i < MYTBF_MAX;i++){
        if (job[i] != NULL){
            job[i]->token += job[i]->csp;
            if (job[i]->token > job[i]->burst){
                job[i]->token = job[i]->burst;
            }
        }
    }
}

//卸载信号处理模块 当发生异常退出时 可以将占用的资源释放 将alarm信号取消
static void mod_unload(){
   //signal(SIGALRM,alarm_status);
   sigaction(SIGALRM,&old_sa,NULL);
    
    struct itimerval itv;
    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 0;
    itv.it_value.tv_usec = 0;
    if(setitimer(ITIMER_REAL,&itv,&old_itv) < 0){
        perror("setitimer()");
        exit(1);
    }

    for (int i = 0;i < MYTBF_MAX;i++){
        free(job[i]);
    }
}

//装载信号处理模块
static void mod_load(){
    //alarm_status = signal(SIGALRM,handler);//保存alarm信号处理函数原来的状态
    struct sigaction sa;
    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;

    if (sigaction(SIGALRM,&sa,&old_sa) < 0){
        perror("sigaction()");
        exit(1);
    }


    struct itimerval itv;
    itv.it_interval.tv_sec = 1;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 1;
    itv.it_value.tv_usec = 0;
    if(setitimer(ITIMER_REAL,&itv,&old_itv) < 0){
        perror("setitimer()");
        exit(1);
    }
    atexit(mod_unload);
}

mytbf_t *mytbf_init(int cps,int burst){
    struct mytbf_st *tbf;

    if (!inited){
        mod_load();
    }

    //将新的tbf装载到任务组中
    int pos;
    pos = get_free_pos();
    if (pos == -1){
        return NULL;
    }

    tbf = malloc(sizeof(*tbf));
    if (tbf == NULL)
        return NULL;
    tbf->token = 0;
    tbf->csp = cps;
    tbf->burst = burst;
    tbf->pos = pos;
    
    job[pos] = tbf;

    return tbf;
}

//获取token ptr是一个 void * size是用户想要获取的token数
int mytbf_fetchtoken(mytbf_t *ptr,int size){
    struct mytbf_st *tbf = ptr;

    if (size <= 0){
        return -EINVAL;
    }
    
    //有token继续
    while (tbf->token <= 0){
        pause();
    }
    
    int n =tbf->token<size?tbf->token:size;

    tbf->token -= n;
    //用户获取了 n 个token
    return n;
}

//归还token ptr是一个 void *
int mytbf_returntoken(mytbf_t *ptr,int size){
    struct mytbf_st *tbf = ptr;

    if (size <= 0){
        return -EINVAL;
    }
    
    tbf->token += size;
    if (tbf->token > tbf->burst)
        tbf->token = tbf->burst;

    return size;
}

int mytbf_destroy(mytbf_t *ptr){
    struct mytbf_st *tbf = ptr;
    job[tbf->pos] = NULL;
    free(tbf);
    return 0;
}

main.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <math.h>

#include "mytbf.h"

static const int SIZE = 1024;
static const int CPS = 10;
static const int BURST = 100;//最大令牌数

static volatile int token = 0;//持有令牌数

int main(int argc,char** argv)
{
    if (argc < 2){
        fprintf(stdout,"Usage...");
        exit(1);
    }

    mytbf_t *tbf;

    tbf = mytbf_init(CPS,BURST);
    if (tbf == NULL){
        fprintf(stderr,"tbf init error");
        exit(1);
    }

    //打开文件
    int sfd,dfd = 0;
    do{
        sfd = open(argv[1],O_RDONLY);
        if (sfd < 0){
            if (errno == EINTR)
              continue;
            fprintf(stderr,"%s\n",strerror(errno));
            exit(1);
        }
    }while(sfd < 0);

    char buf[SIZE];
    
    while(1){
        
        int len,ret,pos = 0;
        int size = mytbf_fetchtoken(tbf,SIZE);
        
        //int i = 0;
        //while(i < 2){
        //    sleep(1);
        //    i++;
        //}

        if (size < 0){
            fprintf(stderr,"mytbf_fetchtoken()%s\n",strerror(-size));
            exit(1);
        }

        len = read(sfd,buf,size);
        while (len < 0){
            if (errno == EINTR)
              continue;
            strerror(errno);
            break;
        }

        //读取结束
        if (len == 0){
            break;
        }

        //要是读到结尾没用完token
        if (size - len > 0){
            mytbf_returntoken(tbf,size-len);
        }

        //以防写入不足
        while(len > 0){
            ret = write(dfd,buf+pos,len);
            while (ret < 0){
                if (errno == EINTR){
                  continue;
                }
                printf("%s\n",strerror(errno));
                exit(1);
            }

            pos += ret;
            len -= ret;
        }
    }

    close(sfd);
    mytbf_destroy(tbf);

    exit(0);
}

信号量的PV操作实例

sem_pv.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sem.h>

#define THRNUM 20
#define FNAME "/tmp/out"
#define BUFSIZE 1024

static int semid;

//取资源量
static void P(){
    struct sembuf op;

    op.sem_num = 0;
    op.sem_op = -1;//取一个资源
    op.sem_flg = 0;//特殊要求

    while(semop(semid,&op,1) < 0){
        if (errno == EINTR||errno == EAGAIN){
            continue;
        }else{
            perror("semop()");
            exit(1);
        }
    }

}

//还资源量
static void V(){
    struct sembuf op;

    op.sem_num = 0;
    op.sem_op = 1;//取一个资源
    op.sem_flg = 0;//特殊要求

    while(semop(semid,&op,1) < 0){
        if (errno == EINTR||errno == EAGAIN){
            continue;
        }else{
            perror("semop()");
            exit(1);
        }
    }

}

static void handler(){
    FILE *fp = fopen(FNAME,"r+");
    char buf[BUFSIZE];


    if(fp == NULL){
        perror("fopen()");
        exit(1);
    }

    P();

    fgets(buf,BUFSIZE,fp);
    fseek(fp,0,SEEK_SET);
    sleep(1);
    fprintf(fp,"%d\n",atoi(buf)+1);
    fflush(fp);

    V();

    fclose(fp);
}

int main()
{
    pid_t pid;

    semid = semget(IPC_PRIVATE,1,0666);//父子关系的进程通信可以使用匿名IPC
    if (semid < 0){
        perror("semget()");
        exit(1);
    }
    //初始化
    if (semctl(semid,0,SETVAL,1)){//相当于互斥量
        perror("semctl()");
        exit(1);

    }    

    for (int i = 0;i < THRNUM;i++){
        pid = fork() ;
        if (pid < 0){
            perror("fork()");
            exit(1);
        }
        if (pid == 0){
            handler();
            exit(0);
        }
    }

    for (int i = 0;i < THRNUM;i++){
        wait(NULL);
    }
    semctl(semid,0,IPC_RMID);

    return 0;
}
相关推荐
凌肖战4 分钟前
力扣上刷题之C语言实现(数组)
c语言·算法·leetcode
李小星同志33 分钟前
高级算法设计与分析 学习笔记6 B树
笔记·学习
霜晨月c44 分钟前
MFC 使用细节
笔记·学习·mfc
王哲晓1 小时前
Linux通过yum安装Docker
java·linux·docker
Jhxbdks1 小时前
C语言中的一些小知识(二)
c语言·开发语言·笔记
小江湖19941 小时前
元数据保护者,Caesium压缩不丢重要信息
运维·学习·软件需求·改行学it
代码雕刻家1 小时前
数据结构-3.1.栈的基本概念
c语言·开发语言·数据结构
gopher95111 小时前
linux驱动开发-中断子系统
linux·运维·驱动开发
AlexMercer10121 小时前
【C++】二、数据类型 (同C)
c语言·开发语言·数据结构·c++·笔记·算法
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net