- 创建两个子进程;
子进程1对文件mytest.txt进行写操作,
每5秒写入一次,
写入当时的时间。
子进程2对文件mytest.txt进行读操作,
每5秒中读一次,
读取并打印文件中所写入的最新时间。
读写操作都使用文件锁进行保护。
main.c
time_t currtime;
time(&currtime);
char *time_string = ctime(&currtime);
- 信号
什么是信号?
信号是一种事件。
不能自定义信号,所有信号都是系统预定义的。
信号由谁产生?
1) 由shell终端根据当前发生的错误(段错误、非法指令等)而产生相应的信号
比如:
socket通信或者管道通信,
如果读端都已经关闭,
执行写操作(或者发送数据),
将导致执行写操作的进程收到SIGPIPE信号
(表示管道破裂)
该信号的默认行为:终止该进程。
2) 在shell终端,使用kill或killall命令产生信号
实例:main1.c
./a.out &
kill -HUP 13733 /* 向PID为13733的进程发送SIGHUP */
3) 在程序代码中,调用kill系统调用产生信号
-
有哪些信号
信号名称 说明
SIGABORT 进程异常终止
SIGALRM 超时告警
SIGFPE 浮点运算异常
SIGHUP 连接挂断
SIGILL 非法指令
SIGINT 终端中断 (Ctrl+C将产生该信号)
SIGKILL *终止进程
SIGPIPE 向没有读进程的管道写数据
SIGQUIT 终端退出(Ctrl+\将产生该信号)
SIGSEGV 无效内存段访问
SIGTERM 终止
SIGUSR1 *用户自定义信号1
SIGUSR2 *用户自定义信号2
————————————–>以上信号如果不被捕获,则进程接受到后都会终止!
SIGCHLD 子进程已停止或退出
SIGCONT *让暂停的进程继续执行
SIGSTOP *停止执行(即“暂停”)
SIGTSTP 中断挂起
SIGTTIN 后台进程尝试读操作
SIGTTOU 后台进程尝试写
- 信号的捕获
信号的捕获,是指,指定接受到某种信号后,去执行指定的函数。
注意:SIGKILL和SIGSTOP不能被捕获,
即,这两种信号的响应动作不能被改变。
信号的安装
1) 使用signal
用法:man 2 signal
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 注:signal的返回类型,和它的第二个参数,都是函数指针类型 signal的参数2可去以下特殊值: SIG_IGN 忽略信号 SIG_DFL 恢复默认行为 实例: main2.c 改变终端中断信号的行为 此时就不能结束该进程了! 只能通过其他终端,给该进程发送一个其他信号,使它终止 #ps ax | grep ./a.out //查询进程号 #kill -HUP 进程号 恢复信号的默认行为 main3.c 使用SIG_DFL时, 仅当第一次调用自定义的行为后 马上使用SIG_DFL就可恢复, 如果连续捕获多次后,就不确定。
2) 使用sigaction
sigaction与signal的区别:
sigaction比signal更“健壮”,建议使用sigaction
用法:man 2 sigaction
结构struct sigaction
struct sigaction {
void (sa_handler)(int); / 信号的响应函数 */
sigset_t sa_mask; /* 屏蔽信号集 */
int sa_flags; /* 当sa_flags中包含 SA_RESETHAND时,接受到该信号并调用
* 指定的信号处理函数执行之后,把该信号的响应行为重置为默认行为SIG_DFL */
…
}
补充: 当sa_mask包含某个信号A时, 则在信号处理函数执行期间,如果发生了该信号A, 则阻塞该信号A(即暂时不响应该信号), 直到信号处理函数执行结束。 即,信号处理函数执行完之后,再响应该信号A 实例:main4.c 用sigaction改变响应动作 即:用sigaction改写main2.c main5.c 用sigaction恢复默认动作 用sigaction改写main3.c
- 信号的发送
信号的发送方式:
在shell终端用快捷键产生信号
使用kill,killall命令。
使用kill函数和alarm函数
1) 使用kill函数
给指定的进程发送指定信号
用法:man 2 kill
注意:
给指定的进程发送信号需要“权限”:
普通用户的进程只能给该用户的其他进程发送信号
root用户可以给所有用户的进程发送信号
kill失败
失败时返回-1
失败原因:
权限不够
信号不存在
指定的进程不存在
实例:main6.c 创建一个子进程 子进程每秒中输出字符串“child process work!" 父进程等待用户输入, 如果用户按下字符A, 则向子进程发信号SIGUSR1, 子进程的输出字符串改为大写 如果用户按下字符a, 则向子进程发信号SIGUSR2, 子进程的输出字符串改为小写
实例:main7.c
“闹钟”
创建一个子进程
子进程在5秒钟之后给父进程发送一个SIGALRM
父进程收到SIGALRM信号之后,“闹铃”(用打印模拟)
2)使用alarm函数
作用:在指定时间之内给该进程本身发送一个SIGALRM信号。
用法:man 2 alarm
注意:时间的单位是“秒”
实际闹钟时间比指定的时间要大一点。
如果参数为0,则取消已设置的闹钟。
如果闹钟时间还没有到,再次调用alarm,则闹钟将重新定时
每个进程最多只能使用一个闹钟。
返回值: 失败:返回-1 成功:返回上次闹钟的剩余时间(秒) 实例:“闹铃” main8.c 用alarm改写main7.c
3) 使用raise
给本进程自身发送信号。
原型: int raise (int sig)
- 发送多个信号:
某进程正在执行某个信号对应的操作函数期间(该信号的安装函数), 如果此时,该进程又多次收到同一个信号(同一种信号值的信号),则: 如果该信号是不可靠信号(<32),则只能再响应一次。 如果该信号是可靠信号(>32),则能再响应多次(不会遗漏)。 但是,都是都必须等该次响应函数执行完之后,才能响应下一次。
某进程正在执行某个信号对应的操作函数期间(该信号的安装函数),
如果此时,该进程收到另一个信号(不同信号值的信号),则:
如果该信号被包含在当前信号的signaction的sa_mask(信号屏蔽集)中,
则不会立即处理该信号。
直到当前的信号处理函数执行完之后,才去执行该信号的处理函数。
否则:
则立即中断当前执行过程(如果处于睡眠,比如sleep, 则立即被唤醒)
而去执行这个新的信号响应。
新的响应执行完之后,再在返回至原来的信号处理函数继续执行。
例:main4_2.c
- 信号集
1). 什么是信号集
信号集,用sigset_t类型表示,实质是一个无符号长整形。
用来表示包含多个信号的集合。
2). 信号集的基本操作
sigemptyset 把信号集清空
sigfillset 把所有已定义的信号填充到指定信号集
sigdelset 从指定的信号集中删除指定的信号
sigaddset 从指定的信号集中添加指定的信号
sigismember 判断指定的信号是否在指定的信号集中 如果是, 返回 1 如果不是, 返回 0 信号无效, 返回-1 详细用法见 man
3) 进程的“信号屏蔽字”
进程的“信号屏蔽字”是一个信号集
想目标进程发送某信号时,如果这个信号在目标进程的信号屏蔽字中,
则目标进程将不会捕获到该信号,即不会执行该信号的处理函数。
当该进程的信号屏蔽字不再包含该信号时,则会捕获这个早已收到的信号(执行对应的函数)
修改进程的“信号屏蔽字” 使用sigprocmask int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 参数: how: SIG_BLOCK 把参数set中的信号添加到信号屏蔽字中 SIG_UNBLOCK 把参数set中的信号从信号屏蔽字中删除 SIG_SETMASK 把参数set中的信号设置为信号屏蔽字 oldset 返回原来的信号屏蔽字 例:main4_3.c
4) 获取未处理的信号
当进程的信号屏蔽字中信号发生时,这些信号不会被该进程响应,
可通过sigpending函数获取这些已经发生了但是没有被处理的信号
用法: man sigpending
返回值:成功则返回0
失败则返回-1
5) 阻塞式等待信号
(1) pause
阻塞进程,直到发生任一信号后
(2) sigsuspend
用指定的参数设置信号屏蔽字,然后阻塞时等待信号的反生。
即,只等待信号屏蔽字之外的信号
6) 使用sigaction结构中的sa_flags标志
sigaction函数的参数使用struct sigaction
struct sigaction结构有sa_flags标志:
SA_RESETHAND 指定的信号处理函数执行之后,
把该信号的响应行为重置为默认行为SIG_DFL
SA_NOCLDSTOP 子进程停止时,不产生SIGCHLD信号
SA_NODEFER 默认情况下,当某信号的处理函数正在执行时,
该信号将被添加到进程的信号屏蔽字中,
以防止当前信号处理函数没有处理完时,
如果又发生该信号而导致该信号处理函数再次运行。
该信号的处理函数执行完之后,再把该信号从该进程的信号屏蔽字中删除。
以上默认设置的原因: 如果信号处理函数是“不可重入”的,则被打断后重新执行,将产生错误! 以上默认设置,可避免同一信号反复出现而使该信号处理函数, 在未执行完的情况下反复执行。 如果使用SA_NODEFER,将使得该信号不会添加到信号屏蔽字中。 SA_RESTART 默认情况下,可中断的Linux的系统调用在执行期间, 如果收到一个信号,则该系统调用将返回一个错误, 并设置errno为EINTR 如果使用SA_RESTART, 则该信号处理函数执行完之后, 被该信号中断的系统调用将被重新执行, 而不是简单地返回一个错误。
- 函数的“可重入”
在信号处理函数中,应只能调用“可重入”的函数。
因为信号处理函数在执行期间,很可能被中断。
不可重入的函数在上次运行未完成就结束后,
当再次运行时,就可能发生问题!
例如 :printf函数就是不可重入的。
所以在信号处理函数中,不应调用printf
解决办法: 在信号处理函数中,设置一个标志, 在信号处理函数之外去判断这个标志, 根据这个标志的状态来使用printf
附录:
Linux的信号:
不可靠信号
1) SIGHUP
2) SIGINT
3) SIGQUIT
4) SIGILL
5) SIGTRAP
6) SIGABRT
7) SIGBUS
8) SIGFPE
9) SIGKILL
10) SIGUSR1
11) SIGSEGV
12) SIGUSR2
13) SIGPIPE
14) SIGALRM
15) SIGTERM
16) SIGSTKFLT
17) SIGCHLD
18) SIGCONT
19) SIGSTOP
20) SIGTSTP
21) SIGTTIN
22) SIGTTOU
23) SIGURG
24) SIGXCPU
25) SIGXFSZ
26) SIGVTALRM
27) SIGPROF
28) SIGWINCH
29) SIGIO
30) SIGPWR
31) SIGSYS
实时信号(可靠信号)
34) SIGRTMIN
35) SIGRTMIN+1
36) SIGRTMIN+2
37) SIGRTMIN+3
38) SIGRTMIN+4
39) SIGRTMIN+5
40) SIGRTMIN+6
41) SIGRTMIN+7
42) SIGRTMIN+8
43) SIGRTMIN+9
44) SIGRTMIN+10
45) SIGRTMIN+11
46) SIGRTMIN+12
47) SIGRTMIN+13
48) SIGRTMIN+14
49) SIGRTMIN+15
50) SIGRTMAX-14
51) SIGRTMAX-13
52) SIGRTMAX-12
53) SIGRTMAX-11
54) SIGRTMAX-10
55) SIGRTMAX-9
56) SIGRTMAX-8
57) SIGRTMAX-7
58) SIGRTMAX-6
59) SIGRTMAX-5
60) SIGRTMAX-4
61) SIGRTMAX-3
62) SIGRTMAX-2
63) SIGRTMAX-1
64) SIGRTMAX
没有定义32和33