目录
[4. sigaction](#4. sigaction)
[Message Queues 消息队列](#Message Queues 消息队列)
[Semaphore Arrays 信号量数组](#Semaphore Arrays 信号量数组)
[Shared Memory Segments 共享内存段](#Shared Memory Segments 共享内存段)
异步(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;
}