Linux系统编程-信号入门3

早,继续记录我的学习心得。

机械的练习:
只是埋头干!我一直在挥着球拍,努力去击球。我一直在看这道数学题,正试着解答。我一直在重复写代码,试图成为技术大牛。

有准确目的的练习:
意味着要比机械的练习更有目的性,考虑更周全,从而让自己变得更专注。

以玩模拟赛车 acc 为例,怎么样才算是有准确目的的练习:

  • 比较明确的目标: 每天跑圈提升 0.5 秒。

  • 更明确的目标: 过某个弯提升多少速度。

  • 更更明确的目标:过某个弯时应在哪里刹车、应如何控制刹车/油门/方向盘,怎么走线等。

学以致用,明确一下本文的目的:

  • 举例说明 Linux 不会对信号进行排队处理。

  • 文末分享《自控力》里提到的一个提升自控力的小技巧:进行有效锻炼 (重点)。

正文目录:

一、待处理的信号 (Pending Signals)
 1. 一个简单的例子 (sig_pending.c)
  1) 分解代码
  2) 运行效果

二、不对待处理的信号进行排队处理
 1. 仍是那个简单的例子 (sig_pending.c)
  1) 运行效果

 2. 查看 Linux 内核里 Signal Pending 相关的实现 (非重点)
  1) 相关数据结构
  2) 信号的产生
  3) 信号的传递

三、相关参考

四、如何高效地学习:了解自控力 (Self-control)
  1. 此书的基本信息
  2. 一个关于提高自控力的小技巧:锻炼身体

一、待处理的信号 (Pending Signals)

如果某进程接受了一个该进程正在阻塞 (blocking) 的信号,那么会将该信号填加到进程的 等待信号集 (set of pending signals) 中。当解除对该信号的阻塞时,会随之将信号传递给此进程。可以使用 sigpending() 确定进程中处于等待状态的是哪些信号。

$ man 2 sigpending
    #include 

    int sigpending(sigset_t *set);

sigpending() 为调用进程返回处于等待状态的信号集,并将其置于参数 set 指向的 sigset_t 中。

1. 一个简单的例子 (sig_pending.c)

1) 分解代码:
1> main():

int main(void)
{
 sigset_t newmask, oldmask, pendmask;

 if (signal(SIGQUIT, sig_quit) == SIG_ERR)
  err_sys("can't catch SIGQUIT");

 /* Block SIGQUIT and save current signal mask. */
 sigemptyset(&newmask);
 sigaddset(&newmask, SIGQUIT);
 if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
  err_sys("SIG_BLOCK error");

    /* SIGQUIT here will remain pending */
 sleep(5);

 if (sigpending(&pendmask) < 0)
  err_sys("sigpending error");
 if (sigismember(&pendmask, SIGQUIT))
  printf("\nSIGQUIT pending\n");

 /* Restore signal mask which unblocks SIGQUIT. */
 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
  err_sys("SIG_SETMASK error");
 printf("SIGQUIT unblocked\n");

    /* SIGQUIT here will terminate with core file */
 sleep(5);

 exit(0);
}

main() 做了 5 件事:

  • 设置 SIGQUIT 的信号处理函数;

  • 屏蔽 SIGQUIT;

  • 睡眠 5 秒,用于等待 SIGQUIT 信号;

  • 睡眠结束,检测 SIGQUIT 是否处于 pending;

  • 解除屏蔽 SIGQUIT;

注意:在设置 SIGQUIT 为阻塞时,我们保存了老的屏蔽字。为了解除对该信号的阻塞,用老的屏蔽字重新设置了进程信号屏蔽字。另一种方法是用 SIG_UNBLOCK 使阻塞的信号不再阻塞。如果编写一个可能由其他人使用的函数,而且需要在函数中阻塞一个信号,则不能用 SIG_UNBLOCK 简单地解除对此信号的阻塞,这是因为此函数的调用者在调用本函数之前可能也阻塞了此信号。

2> 信号处理函数 sig_quit():

static void sig_quit(int signo)
{
 printf("caught SIGQUIT\n");
 if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
  err_sys("can't reset SIGQUIT");
}

2) 运行效果:

$ ./sig_pending 
^\                      // 按下 1 次 ctrl + \ (在5s之内)
SIGQUIT pending         // 从 sleep(5) 返回后
caught SIGQUIT          // 在信号处理程序中
SIGQUIT unblocked       // 从sigprocmask() 返回
^\Quit (core dumped)

2 个值得注意的点:

  • 信号处理函数是在 sigprocmask() unblock 信号返回之前被调用;

  • 用 signal() 设置信号处理函数,信号被处理时,会将信号处置重置为其默认行为。要想在同一信号“再度光临”时再次调用该信号处理器函数,程序员必须在信号处理器内部调用signal(),以显式重建处理器函数,但是这种处理方式是不安全的,真实的项目里应使用 sigaction(),后续的文章会举例讲解。

二、不对待处理的信号进行排队处理

等待信号集只是一个掩码,仅表明一个信号是否发生,而未表明其发生的次数。换言之,如果同一信号在阻塞状态下产生多次,那么会将该信号记录在等待信号集中,并在稍后仅传递一次。后面会介绍实时信号,对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发送给一进程,那么将会多次传递该实时信号,暂不做深入介绍。

1. 仍是那个简单的例子 (sig_pending.c)

为了降低学习难度,跟前面的 Pending Signals 章节使用同一个例子,修改一下测试步骤:

$ ./sig_pending 
^\^\^\                  // 按下 3 次 ctrl + \ (在5s之内)
SIGQUIT pending         // 从 sleep(5) 返回后
caught SIGQUIT          // 只调用了一次信号处理程序
SIGQUIT unblocked       // 从sigprocmask() 返回
^\Quit (core dumped)

第二次运行该程序时,在进程休眠期间产生了 3 次 SIGQUIT 信号,但是取消对该信号的阻塞后,系统只向进程传送了一次 SIGQUIT,从中可以看出在 Linux 系统上没有对信号进行排队处理。

2. 查看 Linux 内核里 Signal Pending 相关的实现 (非重点)

1) 相关数据结构
内核用 struct task_struct 来描述一个进程,struct task_struct 中信号相关的成员 (Linux-4.14):


struct task_struct {
...
 /* Signal handlers: */
 struct signal_struct  *signal;
 struct sighand_struct  *sighand;
 sigset_t   blocked;
 sigset_t   real_blocked;
 /* Restored if set_restore_sigmask() was used: */
 sigset_t   saved_sigmask;
 struct sigpending  pending;
 unsigned long   sas_ss_sp;
 size_t    sas_ss_size;
 unsigned int   sas_ss_flags;
...
};

我们将注意力集中中 struct sigpending pending 上。struct sigpending pending 建立了一个链表,该链表包含了所有已经产生、且有待内核处理的信号,其定义如下:

struct sigpending {
 struct list_head list;
 sigset_t signal;
};
  • 成员 struct list_head list 通过双向链表管理所有待处理信号,每一种待处理的信号对应双向链表中的 1 个 struct sigqueue 节点。

  • 成员 sigset_t signal 是位图 (bit mask,或称位掩码),它指定了仍然有待处理的所有信号的编号。某 1 bit = 1 表示该 bit 对应的信号待处理。sigset_t 所包含的比特位数目要 >= 所支持的信号数目。因此,内核使用了 unsigned long 数组来定义该数据类型:

typedef struct {
 unsigned long sig[_NSIG_WORDS];
} sigset_t;
  • struct sigqueue 的定义如下:

struct sigqueue {
 struct list_head list;
 int flags;
 siginfo_t info;
 ...
};
  • siginfo_t 用于保存信号的额外信息,暂时不用关心。

注意:在 struct sigpending 链表中,struct sigqueue 对应的是一种类型的待处理信号,而不是某一个具体的信号。

示意图:

Linux系统编程-信号入门3_第1张图片

2) 信号的产生
当给进程发送一个信号时,这个信号可能来自内核,也可能来自另外一个进程。

内核里有多个 API 能产生信号,这些 API 最终都会调用 send_signal()。我们重点关注信号是何时被设置为 pending 状态的。

linux/kernel/signal.c:

send_signal()
 __send_signal()
  struct sigqueue *q = __sigqueue_alloc();
  list_add_tail(&q->list, &pending->list); // 将待处理信号添加到 pending 链表中
  sigaddset(&pending->signal, sig); // 在位图中将信号对应的 bit 置 1
  complete_signal(sig, t, group);
   signal_wake_up();

send_signal() 会分配一个新的 struct sigqueue 实例,然后为其填充信号的额外信息,并添加到目标进程的 sigpending 链表且设置位图。

如果信号成功发送,没有被阻塞,就可以用 signal_wake_up() 唤醒目标进程,使得调度器可以选择目标进程运行。

3) 信号的传递:
这些知识放在这篇文章里已经完全超纲了,如果将所有的细节都暴露出来会让初学者感到极度的困惑。

所以,我们只迈出一小步,将仅剩的一点注意力集中在内核在执行信号处理函数前是如何处理 pending 信号的。

在每次由内核态切换到用户态时,内核都会进行信号处理,最终的效果就是调用 do_signal() 函数。

linux/kernel/signal.c:

do_signal()
 get_signal()
  dequeue_signal(current, ¤t->blocked, &ksig->info);
    handle_signal()
  signal_setup_done();
   signal_delivered();
  • dequeue_signal() 是关键点:

dequeue_signal()
 int sig = next_signal(pending, mask);
 collect_signal(sig, pending, info, resched_timer);
  sigdelset(&list->signal, sig); // 取消信号的 pending 状态
  list_del_init(&first->list); // 删除 pending 链表中的 struct sigqueue 节点
  copy_siginfo(info, &first->info);
  • handle_signal() 会操作进程在用户态下的栈,使得在从内核态切换到用户态之后运行信号处理程序,而不是正常的程序代码。

  • do_signal() 返回时,信号处理函数就会被执行。

三、相关参考

  • 《Unix 环境高级编程-第10章 信号》

  • 《Linux/Unix 系统编程手册-第20章 信号:基本概念》

  • 《Linux 系统编程-第10章 信号》

  • 《Linux 程序设计-第11章 进程和信号》

  • 《深入理解 Linux 内核 第11章 信号》

  • 《深入 Linux 内核架构 5.4.1信号》

  • 《Linux 内核源代码情景分析 6.4信号》

四、如何高效地学习:了解自控力 (Self-control)

最近我在阅读一本书:《自控力-斯坦福大学最受欢迎心理学课程》,推荐大家阅读。

1. 该书的基本信息:

基本信息:

作者:  [美] 凯利·麦格尼格尔 / [美] 凯利·麦格尼格尔
出版社: 文化发展出版社(原印刷工业出版社)
出品方: 磨铁图书
副标题: 斯坦福大学最受欢迎心理学课程
原作名: The Willpower Instinct:How Self-control Works,Why it Matters,and What You Can do to Get More of It
译者: 王岑卉
出版年: 2012-8
页数: 263
定价: 39.80元
装帧: 平装
ISBN: 9787514205039
豆瓣评分 8.2 45433人评价

作者:
凯利•麦格尼格尔教授 (Kelly McGonigal, Ph.D.) 是斯坦福大学备受赞誉的心理学家,也是医学健康促进项目的健康教育家。她为专业人士和普通大众开设的心理学课程,包括“意志力科学” (The Science of Willpower) 和“在压力下好好生活” (Living Well with Stress) ,都是斯坦福大学继续教育学院历史上最受欢迎的课程。

目录:

01 我要做,我不要,我想要:什么是意志力?为什么意志力至关重要?
02意志力的本能: 人生来就能抵制奶酪蛋糕的诱惑
03累到无力抵抗:为什么自控力和肌肉一样有极限?
04容忍罪恶:为何善行之后会有恶行?
05大脑的弥天大谎: 为什么我们误把渴望当幸福?
06 “那又如何”:情绪低落为何会使人屈服于诱惑?
07出售未来:及时享乐的经济学
08传染:为什么意志力会传染?
09别读这章:“我不要”力量的局限性

书的内容挺好的,但是我个人认为章节名起得有点不好,降低了阅读欲望。

2. 一个关于提高自控力的小技巧:锻炼身体

提高自控力的 “神药”:
锻炼身体。

这听上去挺操蛋的,大多数人就是想要更多的自控力来促使自己锻炼身体,这里反而要求通过锻炼身体来提升自控力。别急,下面这个关于有效锻炼的观点就有点意思了:

什么样的锻炼最有效: 你愿意去做的锻炼。

如果你设定了一个目标,但一周都坚持不下来的话,那是毫无意义的。而且,对于究竟要锻炼多久,科学研究也没有达成共识。2010年,一项针对10个不同研究的分析发现,改善心情、缓解压力的最有效的锻炼是每次5分钟,而不是每次几小时。

某些科学家认为:5 分钟的 “绿色锻炼” 就能减缓压力、改善心情、提高注意力、增强自控力 。任何能让你走到室外、回到大自然怀抱中的活动,都是绿色锻炼。

如果你坚信自己不适合运动,就把运动的定义扩大一些:
只要是不是坐着的且不在吃垃圾食品的活动就可以认为是在运动,例如扫地、散步、逛街。

鉴于大多数人的注意力无法在一篇文章里上集中太久,更多的内容请大家自行去阅读吧,不是自己理解到的东西是消化不了的。有机会的话我会把更多的读书心得放在后面的文章。

Linux系统编程-信号入门3_第2张图片

Linux系统编程-信号入门3_第3张图片

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。如果你也对 嵌入式系统和开源软件 感兴趣,并且想和更多人互相交流学习的话,请关注我的公众号:嵌入式系统砖家,一起来学习吧,无论是 关注或转发 , 还是赏赐,都是对作者莫大的支持,谢谢 各位的大拇指「在看」 吧,,祝工作顺利,家庭和睦~

你可能感兴趣的:(Linux系统编程-信号入门3)