信号
信号概念
信号是信息的载体,Linux/UNIX环境下,古老、经典的通信方式,现下依然是主要的通信手段。
Unix早期版本就提供了信号机制,但不可靠,信号可能丢失,Berkeley和AT&T都对信号模型做了更改,增加了可靠信号机制,但彼此不兼容。POSIX.1对可靠信号例程进行了标准化。
- 未决:产生与递达之间状态
- 递达:产生并且送达到进程,直接被内核处理掉
- 信号处理方式:执行默认处理动作、忽略、捕捉
- 阻塞信号集(信号屏蔽字):本质是位图,用来记录信号的屏蔽状态,一旦被屏蔽的信号,在解除屏蔽前,一直处于未决状态
- 未决信号集:本质也是位图,用来记录信号的处理状态,该信号集中的信号,表示已经产生,但尚未被处理。
信号特质
由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性,但对于用户来说,这个延迟时间非常短,不易察觉。每个进程收到的所有信号,都是由内核负责发送的,内核处理。
产生信号
- 按键产生:
Ctrl+c
、Ctrl+z
、Ctrl+\
系统调用产生:
kill、raise、abort
软件条件产生:定时器
alarm
- 硬件异常产生:非法访问内存、除0、内存对齐错误
- 命令产生:
kill
命令
信号4要素
和变量三要素类似,每个信号也有其必备四要素,分别是编号、名称、事件和默认处理动作,如下图所示
kill函数
1 | int kill(pid_t pid,int sig) |
通过子进程发信号的方式kill进程
1 |
|
信号集操作函数
1 | sigset_t set 自定义信号集 |
设置信号屏蔽字和解除屏蔽
1 | int sigprocmask(int how,const sigset_t *set,sigset_t *oldset) |
查看未决信号集
1 | int sigpending(sigset_t *set) |
屏蔽SIGINT信号
1 |
|
信号捕捉
signal函数
注册一个信号捕捉函数
1 | typedef void(*sighandler_t)(int); |
使用signal函数捕捉SIGINT信号
1 |
|
sigaction函数
修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
1 | int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact) |
struct sigaction 结构体
1 | struct sigaction { |
使用sigaction函数捕捉SIGINT信号
1 |
|
守护进程
Daemon是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的时间,一般采用以d结尾的名字。Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互,不受用户登陆、注销的影响,一直在运行着,他们都是守护进程。如,预读入缓输出机制的实现,FTP服务器,nfs服务器等。创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader
创建守护进程模型
创建子进程,父进程推出
所有工作在子进程中进行形式上脱离了控制终端
在子进程中创建新会话
setsid()
函数使子进程完全独立出来,脱离控制
改变当前目录为根目录
chdir()
函数防止占用可卸载的文件系统(U盘等)
也可以换成其他路径
重设文件权限掩码
umask()
函数防止继承的文件创建屏蔽字拒绝某些权限
增加守护进程灵活性
关闭文件描述符(0,1,2)
继承的打开文件不会用到,浪费系统资源,无法卸载
创建守护进程代码
1 |
|
线程
线程概念
LWP:light weight process 轻量级的进程,本质仍是进程(Linux环境下)
进程:独立地址空间,拥有PCB
线程:有独立的PCB,但没有独立的地址空间
区别:在于是否共享地址空间
Linux下
- 线程:最小的执行单位
- 进程:最小分配资源单位,可看程师只有一个线程的进程
线程共享资源
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID
- 内存地址空间(.text/.data/.bss/heap/共享库)
线程非共享资源
- 线程id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量
- 信号屏蔽字
- 调度优先级
线程优缺点
优点:提高程序并发性 ,开销小,数据通信共享数据方便
缺点:库函数,不稳定,调试编写困难,gdb不支持,对信号支持不好
线程控制原语
pthread_self函数
获取线程ID,其作用对应进程中getpid
函数
1 | pthread_t pthread_self(void) |
pthread_create函数
创建一个新线程,其作用对应进程中fork函数
1 | int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void*(*start_routine)(void *),void *arg) |
创建一个线程
1 |
|
循环创建多个子线程
1 |
|
pthread_exit函数
将单个线程退出
1 | void pthread_exit(void *retval) |
举例,使用pthread_exit
退出主线程
1 |
|
pthread_join函数
阻塞等待线程退出,获取线程退出状态,其作用对应进程中的waitpid()
函数
1 | int pthread_join(pthread_t thread,void **retval) |
对比记忆
- 进程中
main
返回值、exit
参数—>int
;等待子进程结束wait
函数参数—>int *
- 线程中 线程主函数返回值、
pthread_exit
—>void *
;等待线程结束pthread_jion
函数参数—>void **
使用pthread_join获取子线程返回值
1 |
|
pthread_cancel函数
杀死一个线程,需要到达取消点,如果子线程没有到达取消点,那么pthread_cancel
无效,我们可以在程序中,手动添加一个取消点,使用pthread_testcancel()
,成功被pthread_cancel()
杀死的线程,返回-1
,可以使用pthread_join
回收。
1 | int pthread_cancel(pthread_t thread) |
pthread_detach函数
设置线程分离
1 | int pthread_detach(pthread_t thread) |