在linux中信号量可以看作是一种睡眠锁,允许进程进入睡眠状态,其过程如下:
存在一个 Task获得已占用的信号量,信号量将task推进等待队列让其处于睡眠状态,CPU处理其他任务,当signal被释放,则将处于等待队列睡眠的task唤醒,重获该信号量,由于争用信号量的进程在等待锁重新变为可用时会处于睡眠状态,故信号量适用于锁被长时间持有的状态。
信号量在同一时刻下允许任意数量的锁持有者,分为二进制信号量和计数信号量两种,初始化时将使用者数量计数为1则记为二进制信号量,同一时刻至多有1个CPU持有者,初始化时将使用者数量>1,记为计数信号量,同一时刻至多有count个CPU持有者。
将信号量看作一个计数器,其核心支持两个操作原语P(down)、V(up)操作,其down操作对信号量计数递减去请求获得一个信号量,其递减结果>=0则获得该信号量锁并将任务进入临界区,递减结果<0则将会task放置于等待队列;up操作对信号量计数递增判断当前信号量上的等待队列是否为空,不为空则将处于等待队列的任务唤醒同时获得该信号量。
struct semaphore {
raw_spinlock_t lock; //自旋锁 保护count、wait_list
unsigned int count;//允许进入临界区的内核执行路径个数
struct list_head wait_list;//管理所有基于该信号量睡眠的进程,没有成功获得信号量锁的进程会进入该链表睡眠
};
sema_init()
完成信号量的初始化工作,__SEMAPHORE_INITIALIZER
宏将将sem的的值为val =1 填充semaphore结构。
static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);//初始化锁依赖映射,支持锁依赖检查工具(lockdep)
}
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
其利用__RAW_SPIN_LOCK_UNLOCKED
宏初始化信号量内部的锁,count
设置为n即信号量可以被获取n次,LIST_HEAD_INIT
宏来初始化name的wait_list成员。
int down_interruptible(struct semaphore *sem)
{
unsigned long flags; // 用于保存中断状态的变量
int result = 0;//0表示成功获取信号量
might_sleep();
raw_spin_lock_irqsave(&sem->lock, flags);//关闭本地cpu中断
if (likely(sem->count > 0))//根据信号量计数判断进入临界区
sem->count--;
else//无法获得信号量,将进程推进于等待队列
result = __down_interruptible(sem);//睡眠
raw_spin_unlock_irqrestore(&sem->lock, flags);//释放锁
return result;
}
试图获取指定的信号量,核心操作根据对信号量的计数值count判断是否进入临界区,如果信号量的count>0则表明成功获取了信号量锁且任务可以进入临界区,否则将获取信号量锁失败,任务推进于等待队列,调用__down_interruptible()
执行睡眠操作。
static noinline int __sched __down_interruptible(struct semaphore *sem)
{
return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
执行核心函数为__down_common()
。
static inline int __sched __down_common(struct semaphore *sem, long state,
long timeout)
{
struct semaphore_waiter waiter;//
list_add_tail(&waiter.list, &sem->wait_list);//
waiter.task = current;
waiter.up = false;
for (;;) {
if (signal_pending_state(state, current))
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
__set_current_state(state);
raw_spin_unlock_irq(&sem->lock);
timeout = schedule_timeout(timeout);
raw_spin_lock_irq(&sem->lock);
if (waiter.up)
return 0;
}
timed_out:
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;
}
其中semaphore_waiter
数据结构描述获取信号量失败的进程,list_add_tail()
将当前进程放在sem的wait_list成员中,添加到信号量等待队列尾部,执行for循环,signal_pending_state()
/检查当前进程是否有待处理的信号,对于被超时/其他cpu发送的信号唤醒的进程,跳转interrupted/timed_out
,调用schedule_timeout()
主动让出CPU,根据waiter.up为true,则当前睡眠在等待队列中的进程被信号量的up操作唤醒,进程获得该信号量。
void up(struct semaphore *sem)
{
unsigned long flags;//保存中断状态
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);//唤醒
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
判断当前信号量上的等待队列是否为空,为空则没有进程在等待该信号量,将信号量的计数递增,否则说明有进程在等待队列中处于睡眠状态,调用__up()
将其唤醒。
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list);
list_del(&waiter->list);
waiter->up = true;
wake_up_process(waiter->task);
}
up操作释放信号量与down操作一致,当有进程在等待信号量时,该函数负责将等待队列中基于该信号量的进程唤醒,由于使用的是先进先出队列,所以在
down()
函数中通过list_add_tail()
函数将进程添加置尾部,在up()函数中list_first_entry()
寻找等待队列的第一个等待者,将其出队,设置其标志为true,调用wake_up_process()
函数唤醒waiter->task
进程,判断标准是对waiter->up
的标志进行判断,如果为true则成功唤醒进程并成功获取信号量,否则失败。
每个信号都由一个唯一的数字标识符来表示,这些数字被称为信号编号。信号编号是整数值,通常用正整数来表示,在shell中使用kill -l
查看Linux系统支持的全部信号。
Linux信号机制基本上是从Unix系统中继承过来的,定义在signal.h中,在Linux中没有16和32这两个信号。信号含义如下:
对上述信号进行分类,如下:
程序不可捕获、阻塞 | SIGKILL、SIGSTOP |
---|---|
不能恢复至默认动作 | SIGILL、SIGTRAP |
默认会导致进程流产 | SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGXFSZ |
默认会导致进程停止 | SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU |
默认会导致进程退出 | SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM |
默认进程忽略 | SIGCHLD、SIGPWR、SIGURG、SIGWINCH |
其中SIGHUP~ SIGSYS
继承自UNIX系统,称其不可靠信号,而SIGRTMIN ~ SIGRTMAX
来自POSIX标准标准定义,称其为可靠信号/实时信号。
对于不可靠信号可以将其理解为,当基于信号产生软中断时,内核会在进程表中设置标志,该标志表示内核向一个进程传递了一个信号,假设设定该信号为阻塞,在进程解除该阻塞状态之前,阻塞信号被传递多次则成其为可靠信号,同理被传递了一次称其为不可靠信号。
使用信号目的:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
功能:signal() 函数用于注册信号处理函数,指定在接收到特定信号时要执行的操作。
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
功能:改变进程接收到特定信号后行为。
int raise(int sig)
功能:对当前进程发送指定信号
int pause(void)
功能:将进程挂起等待信号
int kill(pid_t pid,int sig)
功能:通过进程编号发送信号
unsigned int alarm(unsigned int seconds);
功能:指定时间(秒)发送SIGALRM信号。 seconds 为0 时取消所有已设置的alarm请求
int sigqueue(pid_t pid,int sig,const union sigval val)
功能:通过进程编号发送信号
int setitimer(int which,const struct itimerval *value,struct itimerval *oldvalue)
功能:定时发送信号,根据which可指定三种信号类型:SIGALRM、SIGVTALRM 和 SIGPROF
struct itimerval
的成员 it_interval定义间隔时间,it_value 为0时,使计时器失效
void abort(void)
功能:将造成进程终止,除非捕获SIGABORT信号
sigfillset(sigset_t *set) | 设置所有的信号到set信号集中 |
---|---|
sigemptyset(sigset_t *set) | 从set信号集中清空所有信号 |
sigaddset(sigset_t *set,int sig) | 在set信号集中加入sig信号 |
sigdelset(sigset_t *set,int sig) | 在set信号集中删除sig信号 |
int sigprocmask(int how,const sigset_t *set,sigset_t *set) | 根据how值,设置阻塞信号集/释放阻塞的信号集 |
---|---|
int sigpending(sigset_t *set) | 获取在阻塞中的所有信号 |
int sigsuspend(const sigset_t *set); | 将进程挂起等待信号 |
t sigset_t *set,sigset_t *set) | 根据how值,设置阻塞信号集/释放阻塞的信号集 |
---|---|
int sigpending(sigset_t *set) | 获取在阻塞中的所有信号 |
int sigsuspend(const sigset_t *set); | 将进程挂起等待信号 |