Linux系统编程(三)

信号

信号概念

信号是信息的载体,Linux/UNIX环境下,古老、经典的通信方式,现下依然是主要的通信手段。

Unix早期版本就提供了信号机制,但不可靠,信号可能丢失,Berkeley和AT&T都对信号模型做了更改,增加了可靠信号机制,但彼此不兼容。POSIX.1对可靠信号例程进行了标准化。

  • 未决:产生与递达之间状态
  • 递达:产生并且送达到进程,直接被内核处理掉
  • 信号处理方式:执行默认处理动作、忽略、捕捉
  • 阻塞信号集(信号屏蔽字):本质是位图,用来记录信号的屏蔽状态,一旦被屏蔽的信号,在解除屏蔽前,一直处于未决状态
  • 未决信号集:本质也是位图,用来记录信号的处理状态,该信号集中的信号,表示已经产生,但尚未被处理。
信号特质

由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性,但对于用户来说,这个延迟时间非常短,不易察觉。每个进程收到的所有信号,都是由内核负责发送的,内核处理。

产生信号
  • 按键产生:Ctrl+cCtrl+zCtrl+\
  • 系统调用产生:kill、raise、abort

  • 软件条件产生:定时器alarm

  • 硬件异常产生:非法访问内存、除0、内存对齐错误
  • 命令产生:kill命令
信号4要素

和变量三要素类似,每个信号也有其必备四要素,分别是编号、名称、事件和默认处理动作,如下图所示

1

kill函数
1
2
3
4
5
6
7
8
9
int kill(pid_t pid,int sig)

成功:0
失败:-1 设置errno

pid>0 : 发送信号给指定的进程
pid=0 : 发送信号给与调用kill函数进程属于同一进程组的所有进程
pid<-1 : 取|pid|发给对应进程组
pid=-1 : 发送给进程有权限发送的系统中的所有进程
通过子进程发信号的方式kill进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<signal.h>

void sys_err(const char *str){
perror(str);
exit(1);
}

int main(int argc,char *argv[])
{
pid_t pid = fork();
if(pid>0){
printf("parent,pid = %d\n",getpid());
while(1);
} else if(pid == 0){
printf("child pid = %d,ppid = %d\n",getpid(),getppid());
sleep(2);
kill(getppid(),SIGKILL);
}
return 0;
}
信号集操作函数
1
2
3
4
5
6
sigset_t set 自定义信号集
sigemptyset(sigset_t *set) 清空信号集
sigfillset(sigset_t *set) 全部置1
sigaddset(sigset_t *set,int signum) 将一个信号添加到集合中
sigdelset(sigset_t *set,int signum) 将一个信号从集合中移除
sigismember(const sigset_t *set,int signum) 判断一个信号是否在集合中
设置信号屏蔽字和解除屏蔽
1
2
3
4
5
6
7
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset)

how:SIG_BLOCK 设置阻塞
SIG_UNBLOCK 取消阻塞
SIG_SETMASK 用自定义set替换ask
set:自定义set
oldset:旧有的mask
查看未决信号集
1
2
3
int sigpending(sigset_t *set)

set:传出的未决信号集
屏蔽SIGINT信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str){
perror(str);
exit(1);
}

void print_set(sigset_t *set)
{
int i;
for (i=1;i<32;i++){
if(sigismember(set,i))
putchar('1');
else
putchar('0');
}
printf("\n");
}

int main(int argc,char *argv[])
{
sigset_t set,oldset,pedset;
int ret = 0;

sigemptyset(&set);
sigaddset(&set,SIGINT);

ret = sigprocmask(SIG_BLOCK,&set,&oldset);
if(ret == -1)
sys_err("sigprocmask error");

while(1){
ret = sigpending(&pedset);
print_set(&pedset);
sleep(1);
}
return 0;
}

信号捕捉

signal函数

注册一个信号捕捉函数

1
2
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
使用signal函数捕捉SIGINT信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str){
perror(str);
exit(1);
}

void sig_catch(int signo){
printf("catch you!! %d\n",signo);
return ;
}

int main(int argc,char *argv[])
{
signal(SIGINT,sig_catch);
while(1);
return 0;

}
sigaction函数

修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)

1
2
3
4
5
6
7
8
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)

成功:0
失败:-1 设置errno

参数
act: 传入参数,新的处理方式
oldact: 传出参数,旧的处理方式

struct sigaction 结构体

1
2
3
4
5
6
7
struct sigaction {
void (*sa_handler)(int); //指定信号捕捉后的处理函数名
void (*sa_sigaction)(int,siginfo_t*,void*);
sigset_t samask; //调用信号处理函数时,所要屏蔽的信号集合
int sa_flags; //通常设置为0 表示默认属性
void (*sa_restorer)(void);
}
使用sigaction函数捕捉SIGINT信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str){
perror(str);
exit(1);
}

void sig_catch(int signo){
printf("catch you!! %d\n",signo);
return ;
}

int main(int argc,char *argv[])
{
struct sigaction act,oldact;
act.sa_handler = sig_catch; //set callback function name
sigemptyset(&(act.sa_mask)); //set mask when sig_catch working
act.sa_flags = 0; //usually use

int ret = sigaction(SIGINT,&act,&oldact); //注册信号捕捉函数
if(ret == -1)
sys_err("sigaction error");

while(1);
return 0;
}

守护进程

Daemon是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的时间,一般采用以d结尾的名字。Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互,不受用户登陆、注销的影响,一直在运行着,他们都是守护进程。如,预读入缓输出机制的实现,FTP服务器,nfs服务器等。创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader

创建守护进程模型
  • 创建子进程,父进程推出

    所有工作在子进程中进行形式上脱离了控制终端

  • 在子进程中创建新会话

    setsid()函数

    使子进程完全独立出来,脱离控制

  • 改变当前目录为根目录

    chdir()函数

    防止占用可卸载的文件系统(U盘等)

    也可以换成其他路径

  • 重设文件权限掩码

    umask()函数

    防止继承的文件创建屏蔽字拒绝某些权限

    增加守护进程灵活性

  • 关闭文件描述符(0,1,2)

    继承的打开文件不会用到,浪费系统资源,无法卸载

创建守护进程代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<stdio.h>
#include<sys/stat.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str){
perror(str);
exit(1);
}

int main(int argc,char *argv[])
{
pid_t pid;
int ret,fd;
pid = fork();
if(pid>0)
exit(0); //父进程终止

pid = setsid(); //创建新会话
if(pid == -1)
sys_err("setsid error");
ret = chdir("/Users/caoyifan/Desktop/test"); //改变工作目录位置
if(ret == -1)
sys_err("chdir error");
umask(0022); //改变文件访问权限掩码
close(STDIN_FILENO); //关闭文件描述符0
fd = open("/dev/null",O_RDWR); // fd=0
if(fd == -1)
sys_err("open error");
dup2(fd,STDOUT_FILENO); //重定向stdout和stderr
dup2(fd,STDERR_FILENO);

while(1); //模拟守护进程业务
return 0;

}

线程

线程概念

LWP:light weight process 轻量级的进程,本质仍是进程(Linux环境下)

进程:独立地址空间,拥有PCB

线程:有独立的PCB,但没有独立的地址空间

区别:在于是否共享地址空间

Linux下

  • 线程:最小的执行单位
  • 进程:最小分配资源单位,可看程师只有一个线程的进程
线程共享资源
  • 文件描述符表
  • 每种信号的处理方式
  • 当前工作目录
  • 用户ID和组ID
  • 内存地址空间(.text/.data/.bss/heap/共享库)
线程非共享资源
  • 线程id
  • 处理器现场和栈指针(内核栈)
  • 独立的栈空间(用户空间栈)
  • errno变量
  • 信号屏蔽字
  • 调度优先级
线程优缺点

优点:提高程序并发性 ,开销小,数据通信共享数据方便

缺点:库函数,不稳定,调试编写困难,gdb不支持,对信号支持不好

线程控制原语

pthread_self函数

获取线程ID,其作用对应进程中getpid函数

1
2
3
4
5
pthread_t pthread_self(void)

返回值
成功:0
失败:无
pthread_create函数

创建一个新线程,其作用对应进程中fork函数

1
2
3
4
5
6
7
8
9
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void*(*start_routine)(void *),void *arg)

返回值
成功:0
失败:错误号
参数
pthread_t:当前Linux中可理解为:typedef unsignd long int pthread_t
参数1:传出参数,保存系统为我们分配好的线程ID
参数2:通常传NULL,表示使用线程默认属性,若想使用具体属性也可以修改该参数
创建一个线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str){
perror(str);
exit(1);
}
void *tfn(void *arg)
{
printf("thread : pid = %d,tid = %lu\n",getpid(),pthread_self());
return NULL;
}
int main(int argc,char *argv[])
{
pthread_t tid;
//tid = pthread_self();
printf("main : pid = %d,tid = %lu\n",getpid(),pthread_self());
int ret = pthread_create(&tid,NULL,tfn,NULL);
if(ret != 0){
perror("pthread_create error");
}
sleep(1);
return 0;
}
循环创建多个子线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str){
perror(str);
exit(1);
}
void *tfn(void *arg)
{
int i = (int)arg;
sleep(i);
printf("--I'm %dth thread: pid = %d,tid = %lu\n",i+1,getpid(),pthread_self());
return NULL;
}

int main(int argc,char *argv[])
{
int i,ret;
pthread_t tid;
for(i=0;i<5;i++){
ret = pthread_create(&tid,NULL,tfn,(void *)i);
if(ret != 0)
sys_err("pthread_create error");
}
sleep(i);
printf("main: I'm Main,pid = %d,tid = %lu\n",getpid(),pthread_self());
return 0;
}
pthread_exit函数

将单个线程退出

1
2
3
void pthread_exit(void *retval)

参数:retval表示线程退出状态,通常传NULL

举例,使用pthread_exit退出主线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str){
perror(str);
exit(1);
}
void *tfn(void *arg)
{
printf("thread : pid = %d,tid = %ld\n",getpid(),pthread_self());
return NULL;
}
int main(int argc,char *argv[])
{
pthread_t tid;
printf("main : pid = %d,tid = %ld\n",getpid(),pthread_self());
int ret = pthread_create(&tid,NULL,tfn,NULL);
if(ret != 0){
perror("pthread_create error");
}
pthread_exit((void*)0);
}
pthread_join函数

阻塞等待线程退出,获取线程退出状态,其作用对应进程中的waitpid()函数

1
2
3
4
5
6
7
8
int pthread_join(pthread_t thread,void **retval)

成功:0
失败:错误号

参数
thread:线程ID
retval:存储线程结束状态

对比记忆

  • 进程中 main返回值、exit参数—>int;等待子进程结束 wait 函数参数—>int *
  • 线程中 线程主函数返回值、pthread_exit—>void *;等待线程结束 pthread_jion 函数参数—>void **

使用pthread_join获取子线程返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
#include<string.h>
struct thrd {
int var;
char str[256];
};

void sys_err(const char *str){
perror(str);
exit(1);
}

void *tfn(void *arg)
{
struct thrd *tval;
tval = malloc(sizeof(tval));
tval->var = 100;
strcpy(tval->str,"hello thread");
return (void*)tval;
}

int main(int argc,char *argv[])
{
pthread_t tid;
int ret;
struct thrd *retval;
ret = pthread_create(&tid,NULL,tfn,NULL);
if(ret != 0)
sys_err("pthread_create error");
ret = pthread_join(tid,(void**)&retval);
if(ret != 0)
sys_err("pthread_join error");
printf("child thread exit with var=%d,str=%s\n",retval->var,retval->str);
pthread_exit(NULL);
//return 0;
}
pthread_cancel函数

杀死一个线程,需要到达取消点,如果子线程没有到达取消点,那么pthread_cancel无效,我们可以在程序中,手动添加一个取消点,使用pthread_testcancel(),成功被pthread_cancel()杀死的线程,返回-1,可以使用pthread_join回收。

1
int pthread_cancel(pthread_t thread)
pthread_detach函数

设置线程分离

1
2
3
4
5
6
7
int pthread_detach(pthread_t thread)

返回值:
成功:0
失败:errno

thread:待分离的线程id