早,继续记录我的学习心得。
机械的练习:
只是埋头干!我一直在挥着球拍,努力去击球。我一直在看这道数学题,正试着解答。我一直在重复写代码,试图成为技术大牛。
有准确目的的练习:
意味着要比机械的练习更有目的性,考虑更周全,从而让自己变得更专注。
以玩模拟赛车 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. 一个关于提高自控力的小技巧:锻炼身体
如果某进程接受了一个该进程正在阻塞 (blocking) 的信号,那么会将该信号填加到进程的 等待信号集 (set of pending signals) 中。当解除对该信号的阻塞时,会随之将信号传递给此进程。可以使用 sigpending() 确定进程中处于等待状态的是哪些信号。
$ man 2 sigpending
#include
int sigpending(sigset_t *set);
sigpending() 为调用进程返回处于等待状态的信号集,并将其置于参数 set 指向的 sigset_t 中。
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(),后续的文章会举例讲解。
等待信号集只是一个掩码,仅表明一个信号是否发生,而未表明其发生的次数。换言之,如果同一信号在阻塞状态下产生多次,那么会将该信号记录在等待信号集中,并在稍后仅传递一次。后面会介绍实时信号,对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发送给一进程,那么将会多次传递该实时信号,暂不做深入介绍。
为了降低学习难度,跟前面的 Pending Signals 章节使用同一个例子,修改一下测试步骤:
$ ./sig_pending
^\^\^\ // 按下 3 次 ctrl + \ (在5s之内)
SIGQUIT pending // 从 sleep(5) 返回后
caught SIGQUIT // 只调用了一次信号处理程序
SIGQUIT unblocked // 从sigprocmask() 返回
^\Quit (core dumped)
第二次运行该程序时,在进程休眠期间产生了 3 次 SIGQUIT 信号,但是取消对该信号的阻塞后,系统只向进程传送了一次 SIGQUIT,从中可以看出在 Linux 系统上没有对信号进行排队处理。
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 对应的是一种类型的待处理信号,而不是某一个具体的信号。
示意图:
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信号》
最近我在阅读一本书:《自控力-斯坦福大学最受欢迎心理学课程》,推荐大家阅读。
基本信息:
作者: [美] 凯利·麦格尼格尔 / [美] 凯利·麦格尼格尔
出版社: 文化发展出版社(原印刷工业出版社)
出品方: 磨铁图书
副标题: 斯坦福大学最受欢迎心理学课程
原作名: 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别读这章:“我不要”力量的局限性
书的内容挺好的,但是我个人认为章节名起得有点不好,降低了阅读欲望。
提高自控力的 “神药”:
锻炼身体。
这听上去挺操蛋的,大多数人就是想要更多的自控力来促使自己锻炼身体,这里反而要求通过锻炼身体来提升自控力。别急,下面这个关于有效锻炼的观点就有点意思了:
什么样的锻炼最有效: 你愿意去做的锻炼。
如果你设定了一个目标,但一周都坚持不下来的话,那是毫无意义的。而且,对于究竟要锻炼多久,科学研究也没有达成共识。2010年,一项针对10个不同研究的分析发现,改善心情、缓解压力的最有效的锻炼是每次5分钟,而不是每次几小时。
某些科学家认为:5 分钟的 “绿色锻炼” 就能减缓压力、改善心情、提高注意力、增强自控力 。任何能让你走到室外、回到大自然怀抱中的活动,都是绿色锻炼。
如果你坚信自己不适合运动,就把运动的定义扩大一些:
只要是不是坐着的且不在吃垃圾食品的活动就可以认为是在运动,例如扫地、散步、逛街。
鉴于大多数人的注意力无法在一篇文章里上集中太久,更多的内容请大家自行去阅读吧,不是自己理解到的东西是消化不了的。有机会的话我会把更多的读书心得放在后面的文章。
你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。如果你也对 嵌入式系统和开源软件 感兴趣,并且想和更多人互相交流学习的话,请关注我的公众号:嵌入式系统砖家,一起来学习吧,无论是 关注或转发 , 还是赏赐,都是对作者莫大的支持,谢谢 各位的大拇指「在看」 吧,,祝工作顺利,家庭和睦~