Linux系统编程-信号入门3

    技术2022-07-10  157

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

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

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

    以玩模拟赛车 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 <signal.h>     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):

    <sched.h> 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 对应的是一种类型的待处理信号,而不是某一个具体的信号。

    示意图:

    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 分钟的 “绿色锻炼” 就能减缓压力、改善心情、提高注意力、增强自控力 。任何能让你走到室外、回到大自然怀抱中的活动,都是绿色锻炼。

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

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

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

    Processed: 0.012, SQL: 9