信号灯(semaphore)
临界资源
- 一次只允许一个进程使用的资源称为临界资源;
临界资源并不全是硬件或是软件,而是两者都能作为临界资源。
比如硬件的有:打印机、磁带机等;
软件有: 消息缓冲队列、变量、数组、缓冲区等; - 临界区(critical region)
访问共享变量的程序代码段称为临界区,也称为临界段(criticalsection) ;
- 进程互斥
两个或两个以上的进程不能同时进入关于同一组共享变量的临界区,否可可能会发生与时间有关的错误,这种现象称为进程互斥;
semaphore
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。信号灯种类:
- posix有名信号灯(可用于线程、进程同步)
- posix基于内存的信号灯(无名信号灯)
- System V信号灯(IPC对象)
System V信号灯
System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。
而Posix信号灯指的是单个计数信号灯
System V 信号灯由内核维护
二值信号灯、计数信号灯
二值信号灯:
• 值为0或1。与互斥锁类似,资源可用时值为1,不可用时值为0
计数信号灯:
• 值在0到n之间。用来统计资源,其值代表可用资源数
PV操作
通常把信号量操作抽象成PV操作
P
等待操作是等待信号灯的值变为大于0,然后将其减1;
V
释放操作则相反,用来唤醒等待资源的进程或者线程
sem函数
semget
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
函数参数
key:和信号灯集关联的key值
nsems: 信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限,通常为IPC_CREAT | 0666
函数返回值
成功:信号灯集ID
出错:-1
semctl
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl ( int semid, int semnum, int cmd…/union semun arg/);
函数参数
semid:信号灯集ID
semnum: 要修改的信号灯编号
cmd:
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
union semun { short val; /SETVAL用的值/ struct semid_ds* buf; /IPC_STAT、IPC_SET用的semid_ds结构/ unsigned short* array; /SETALL、GETALL用的数组值/ struct seminfo *buf; /为控制IPC_INFO提供的缓存/ } arg;
函数返回值
成功:0
出错:-1错误原因存于errno中
semop
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop ( int semid, struct sembuf *opsptr, size_t nops);
函数参数
semid:信号灯集ID
struct sembuf { short sem_num; // 要操作的信号灯的编号 short sem_op; // 0 : 等待,直到信号灯的值变成0 // 1 : 释放资源,V操作 // -1 : 分配资源,P操作 short sem_flg; // 0, IPC_NOWAIT, SEM_UNDO };
nops: 要操作的信号灯的个数
函数返回值
成功:0
出错:-1
为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量。
举例
sem.h
#ifndef __SEM_H__ #define __SEM_H__ #if 0 union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; #endif int init_sem(int semid, int num, int val) { union semun myun; myun.val = val; if(semctl(semid, num, SETVAL, myun) < 0) { perror("semctl"); exit(1); } return 0; } int sem_p(int semid, int num) { struct sembuf mybuf; mybuf.sem_num = num; mybuf.sem_op = -1; mybuf.sem_flg = SEM_UNDO; if(semop(semid, &mybuf, 1) < 0) { perror("semop"); exit(1); } return 0; } int sem_v(int semid, int num) { struct sembuf mybuf; mybuf.sem_num = num; mybuf.sem_op = 1; mybuf.sem_flg = SEM_UNDO; if(semop(semid, &mybuf, 1) < 0) { perror("semop"); exit(1); } return 0; } #endif
client
#include <sys/types.h> #include <linux/sem.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "sem.h" int main(void) { key_t key; //sem key int semid, semval; //get semaphore if((key = ftok("/app",'i')) <0) { perror("ftok"); exit(1); } printf("key = %x\n",key); if((semid = semget(key, 1, 0666)) < 0) { perror("semget"); exit(1); } //get semaphore value every 1 seconds while (1) { if ((semval = semctl(semid, 0, GETVAL, 0)) == -1) { perror("semctl error!\n"); exit(1); } if (semval > 0) { printf("Still %d resources can be used\n", semval); }else { printf("No more resources can be used!\n"); //break; } sleep(1); } exit(0); }
server
#include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <linux/sem.h> #include <stdio.h> #include <errno.h> #include "sem.h" #define MAX_RESOURCE 10 int main(void) { key_t key_info; int semid; //get semaphore if ((key_info = ftok ("/app", 'i')) < 0) { perror ("ftok info"); exit (-1); } printf("key is %d\n", key_info); if ((semid = semget (key_info, 1, IPC_CREAT | IPC_EXCL |0666)) < 0) { if (errno == EEXIST) { semid = semget (key_info, 1, 0666); } else { perror ("semget"); exit (-1); } } else { init_sem (semid, 0, 1); } //substract 1 every 3 seconds until semaphore value is -1 while (1) { //p printf("p\n"); sem_p (semid, 0); //进入临界区 sleep(4); //V printf("v\n"); sem_v (semid, 0); sleep(3); } exit(0); }
信号量与共享内存
信号
当我们按下ctrl+c终止主控程序时,希望能够实现如下操作:
1.进程退出
2.释放所有申请的资源
互斥锁、条件变量、消息队列、共享内存、信号量、线程、设备文件描述符(摄像头、串口)
信号通信
• 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
• 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
• 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;
• 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程
信号的生存周期
用户进程对信号的响应方式:
• 忽略信号:• 对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
• 捕捉信号:• 定义信号处理函数,当信号发生时,执行相应的处理函数。
• 执行缺省操作:• Linux对每种信号都规定了默认操作
信号处理流程
信号类型
- SIGHUP
含义
该信号在用户终端连接(正常或非正常)结束时发出,
通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。
默认操作
终止
- SIGINT
该信号在用户键入INTR字符(通常是Ctrl-C)时发出, 终端驱动程序发送此信号并送到前台进程中的每一个进程。
终止
- SIGQUIT
该信号和SIGINT类似,但由QUIT字符(通常是 Ctrl-)来控制。
终止 - SIGILL
该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。
终止 - SIGFPE
该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。
终止 - SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
终止 - SIGALRM
该信号当一个定时器到时的时候发出。
终止 - SIGSTOP
该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
暂停进程 - SIGTSTP
该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。
暂停进程 - SIGCHLD
子进程改变状态时,父进程会收到这个信号
忽略 - SIGABORT
该信号用于结束进程
终止
信号发送
kill()和raise()
• kill函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(实际上, kill系统命令只是kill函数的一个用户接口)。
• kill – l 命令查看系统支持的信号列表
• Raise 函数允许进程向自己发送信号
NAME raise - send a signal to the caller SYNOPSIS #include <signal.h> int raise(int sig); DESCRIPTION The raise() function sends a signal to the calling process or thread. In a single-threaded program it is equivalent to kill(getpid(), sig); In a multithreaded program it is equivalent to pthread_kill(pthread_self(), sig); If the signal causes a handler to be called, raise() will return only after the signal handler has returned.
设置信号的处理方式
• 一个进程可以设定对信号的相应方式
• 信号处理的主要方法有两种
使用简单的signal()函数
使用信号集函数族sigaction
• signal()
使用signal函数处理时,需指定要处理的信号和处理函数
使用简单、易于理解
信号设置函数-signal
#include <signal.h> void (*signal(int signum, void (*handler)(int)))(int); //void (*handler)(int)函数指针
signum:指定信号
handler:
SIG_IGN:忽略该信号。
SIG_DFL:采用系统默认方式处理信号。
自定义的信号处理函数指针
函数返回值
成功:设置之前的信号处理方式
出错:-1
举例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/types.h> void sig_handler(int signo); int main(int argc, char *argv[]) { int time_left; #if 1 if (signal(SIGINT, sig_handler) == SIG_ERR) { perror("signal error"); return -1; } signal(SIGQUIT, sig_handler); #endif printf("begin sleep ...\n"); time_left = sleep(10); printf("end sleep ... time_left=%d\n", time_left); while(1); return 0; } void sig_handler(int signo) { if (signo == SIGINT) printf("I received a SIGINT!\n"); signal(SIGINT, SIG_DFL); if (signo == SIGQUIT) printf("I received a SIGQUIT!\n"); }
进程间通讯方式比较
• signa(信号)l: 唯一的异步通信方式
• msg(消息队列):常用于cs模式中, 按消息类型访问,可有优先级
• shm(共享内存):效率最高(直接访问内存) ,需要同步、互斥机制
• sem(信号量):配合共享内存使用,用以实现同步和互斥
• pipe(管道): 具有亲缘关系的进程间,单工,数据在内存中
• fifo: 可用于任意进程间,双工,有文件名,数据在内存,mkfifo file
• Socket:不同主机之间进程通信