信号量基础总结

信号量

在linux中信号量可以看作是一种睡眠锁,允许进程进入睡眠状态,其过程如下:

信号量基础总结_第1张图片

存在一个 Task获得已占用的信号量,信号量将task推进等待队列让其处于睡眠状态,CPU处理其他任务,当signal被释放,则将处于等待队列睡眠的task唤醒,重获该信号量,由于争用信号量的进程在等待锁重新变为可用时会处于睡眠状态,故信号量适用于锁被长时间持有的状态。

信号量在同一时刻下允许任意数量的锁持有者,分为二进制信号量和计数信号量两种,初始化时将使用者数量计数为1则记为二进制信号量,同一时刻至多有1个CPU持有者,初始化时将使用者数量>1,记为计数信号量,同一时刻至多有count个CPU持有者。

将信号量看作一个计数器,其核心支持两个操作原语P(down)、V(up)操作,其down操作对信号量计数递减去请求获得一个信号量,其递减结果>=0则获得该信号量锁并将任务进入临界区,递减结果<0则将会task放置于等待队列;up操作对信号量计数递增判断当前信号量上的等待队列是否为空,不为空则将处于等待队列的任务唤醒同时获得该信号量。
信号量基础总结_第2张图片

semaphore数据结构:

struct semaphore {

  raw_spinlock_t    lock; //自旋锁  保护count、wait_list

  unsigned int     count;//允许进入临界区的内核执行路径个数

  struct list_head   wait_list;//管理所有基于该信号量睡眠的进程,没有成功获得信号量锁的进程会进入该链表睡眠

};

sema_init

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成员。

down_interruptible

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()

__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操作唤醒,进程获得该信号量。

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则成功唤醒进程并成功获取信号量,否则失败。

信号在Linux中的标识

每个信号都由一个唯一的数字标识符来表示,这些数字被称为信号编号。信号编号是整数值,通常用正整数来表示,在shell中使用kill -l查看Linux系统支持的全部信号。
信号量基础总结_第3张图片

Linux信号机制基本上是从Unix系统中继承过来的,定义在signal.h中,在Linux中没有16和32这两个信号。信号含义如下:

  • SIGHUP:当用户退出Shell时,由该Shell启的发所有进程都退接收到这个信号,默认动作为终止进程。
  • SIGINT:用户按下组合键时,用户端时向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
  • SIGQUIT:当用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程并产生core文件。
  • SIGILL :CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件。
  • SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止进程并产生core文件。
  • SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
  • SIGBUS:非法访问内存地址,包括内存地址对齐(alignment)出错,默认动作为终止进程并产生core文件。
  • SIGFPE:在发生致命的算术错误时产生。不仅包括浮点运行错误,还包括溢出及除数为0等所有的算术错误。默认动作为终止进程并产生core文件。
  • SIGKILL:无条件终止进程。本信号不能被忽略、处理和阻塞。默认动作为终止进程。它向系统管理员提供了一种可以杀死任何进程的方法。
  • SIGUSR1:用户定义的信号,即程序可以在程序中定义并使用该信号。默认动作为终止进程。
  • SIGSEGV:指示进程进行了无效的内存访问。默认动作为终止进程并使用该信号。默认动作为终止进程。
  • SIGUSR2:这是另外一个用户定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
  • SIGPIPE:Broken pipe:向一个没有读端的管道写数据。默认动作为终止进程。
  • SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
  • SIGTERM:程序结束(terminate)信号,与SIGKILL不同的是,该信号可以被阻塞和处理。通常用来要求程序正常退出。执行Shell命令kill时,缺少产生这个信号。默认动作为终止进程。
  • SIGCHLD:子程序结束时,父进程会收到这个信号。默认动作为忽略该信号。
  • SIGCONT:让一个暂停的进程继续执行。
  • SIGSTOP:停止(stopped)进程的执行。注意它和SIGTERM以及SIGINT的区别:该进程还未结束,只是暂停执行。本信号不能被忽略、处理和阻塞。默认作为暂停进程。
  • SIGTSTP:停止进程的动作,但该信号可以被处理和忽略。按下组合键时发出该信号。默认动作为暂停进程。
  • SIGTTIN:当后台进程要从用户终端读数据时,该终端中的所有进程会收到SIGTTIN信号。默认动作为暂停进程。
  • SIGTTOU:该信号类似于SIGTIN,在后台进程要向终端输出数据时产生。默认动作为暂停进程。
  • SIGURG:套接字(socket)上有紧急数据时,向当前正在运行的进程发出此信号,报告有紧急数据到达。默认动作为忽略该信号。
  • SIGXCPU:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认动作为终止进程。
  • SIGXFSZ:超过文件最大长度的限制。默认动作为yl终止进程并产生core文件。
  • SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是它只计算该进程占有用的CPU时间。默认动作为终止进程。
  • SIGPROF:类似于SIGVTALRM,它不仅包括该进程占用的CPU时间还抱括执行系统调用的时间。默认动作为终止进程。
  • SIGWINCH:窗口大小改变时发出。默认动作为忽略该信号。
  • SIGIO:此信号向进程指示发出一个异步IO事件。默认动作为忽略。
  • SIGPWR:关机。默认动作为终止进程。
  • SIGRTMIN~SIGRTMAX:Linux的实时信号,它没有固定的含义(或者说可以由用户自由使用)。注意,Linux线程机制使用了前3个实时信号。所有的实时信号的默认动作都是终止进程。

对上述信号进行分类,如下:

程序不可捕获、阻塞 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标准标准定义,称其为可靠信号/实时信号。

对于不可靠信号可以将其理解为,当基于信号产生软中断时,内核会在进程表中设置标志,该标志表示内核向一个进程传递了一个信号,假设设定该信号为阻塞,在进程解除该阻塞状态之前,阻塞信号被传递多次则成其为可靠信号,同理被传递了一次称其为不可靠信号。

使用信号目的:

  • 让进程知道已经发生了一个特定的事件;
  • 强迫进程执行它自己代码中的信号处理程序。

信号处理相关函数

signal()

typedef void (*sighandler_t)(int)sighandler_t signal(int signum, sighandler_t handler)); 

功能:signal() 函数用于注册信号处理函数,指定在接收到特定信号时要执行的操作。

  • 第一个参数指定信号的值
  • 第二个参数指向信号处理函数的指针,可以是自定义函数或内置的信号处理函数。返回值:返回一个函数指针,指向以前注册的信号处理函数。

sigaction()

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

功能:改变进程接收到特定信号后行为。

  • 第一个参数为信号的值
  • 第二个参数是指向结构sigaction的一个实例的指针,指定了对特定信号的处理
  • 第三个参数指向的对象用来保存原来对相应信号的处理

raise()

 int raise(int sig) 

功能:对当前进程发送指定信号

pause()

int pause(void)

功能:将进程挂起等待信号

kill()

int kill(pid_t pid,int sig)

功能:通过进程编号发送信号

alarm()

unsigned int alarm(unsigned int seconds);

功能:指定时间(秒)发送SIGALRM信号。 seconds 为0 时取消所有已设置的alarm请求

sigqueue()

int sigqueue(pid_t pid,int sig,const union sigval val)

功能:通过进程编号发送信号

setitimer()

int setitimer(int which,const struct itimerval *value,struct itimerval *oldvalue)

功能:定时发送信号,根据which可指定三种信号类型:SIGALRM、SIGVTALRM 和 SIGPROF

struct itimerval 的成员 it_interval定义间隔时间,it_value 为0时,使计时器失效

abort()

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); 将进程挂起等待信号

你可能感兴趣的:(学习笔记,windows,linux,学习)