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;
}
相关推荐
watermelonoops27 分钟前
Deepin和Windows传文件(Xftp,WinSCP)
linux·ssh·deepin·winscp·xftp
疯狂飙车的蜗牛1 小时前
从零玩转CanMV-K230(4)-小核Linux驱动开发参考
linux·运维·驱动开发
远游客07134 小时前
centos stream 8下载安装遇到的坑
linux·服务器·centos
马甲是掉不了一点的<.<4 小时前
本地电脑使用命令行上传文件至远程服务器
linux·scp·cmd·远程文件上传
jingyu飞鸟4 小时前
centos-stream9系统安装docker
linux·docker·centos
XH华4 小时前
初识C语言之二维数组(下)
c语言·算法
超爱吃士力架4 小时前
邀请逻辑
java·linux·后端
南宫生4 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__4 小时前
Web APIs学习 (操作DOM BOM)
学习
数据的世界017 小时前
.NET开发人员学习书籍推荐
学习·.net