Linux系统编程(五)并发(信号、线程)

目录

一、信号

1.1 信号的概念

1.2 signal()

1.3 可重入函数

1.4 信号的响应过程(重点)

1.5 信号相关函数(kill、raise、alarm、pause、abort)

1.6 信号集

二、线程  

2.1 线程的概念

2.2 线程的创建、终止,栈的清理

2.3 线程同步(互斥量、条件变量、信号量、读写锁)

2.4 线程属性,线程同步的属性

2.5 openmp 线程标准(相对于 posix 线程标准)


一、信号

1.1 信号的概念

信号是软件层面的中断。信号的响应依赖于中断。信号分为标准信号和实时信号。

kill -l 可以查看系统中的信号:

core 文件是程序出错的现场,可以使用 gdb 对 core 文件进行调试。 

1.2 signal()

signal(2) 可以为特定的信号 signum 注册一个新的处理函数 handler,并且返回之前的处理函数。当出现特定的信号 signum 就会调用 handler。假如 handler 为 SIGIGN,则信号会被忽视;若 handler 为 SIGDFL,则会执行默认的处理函数。

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

该函数实际的样子:

例子

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void sig_handler(int signum)
{
  write(1, "1", 1);
}

int main()
{
  // signal(SIGINT, SIGIGN);
  signal(SIGINT, sig_handler);

  for (int i = 0; i < 10; i++) {
    write(1, "*", 1);
    sleep(1);
  }
  exit(0);
}

 ctrl + c 可以发出 SIGINT 信号,所以每次使用 ctrl + c 都会调用一次信号处理函数 sig_handler:

重点:信号会打断阻塞的系统调用!!

如果 ctrl + c 按的很快的话可以看到 sleep 系统调用会被打断。 比如 open 和 read 系统调用中的两个错误码:

所以在前面例子中系统调用失败可能是由于信号导致的假错误,这时候我们可以重新进行一次系统调用。 

不能随意地在信号处理函数中往外跳。

1.3 可重入函数

信号的不可靠,比如说第一次调用还没结束,第二次调用就开始了(连续两个相同信号到来)。可以使用可重入函数解决,可重入函数在第一次调用还没结束时发生第二次调用不会出错。

所有的系统调用都是可重入的,部分库函数是可重入的

memcpy() 的两个内存地址空间不能重叠,而 memmove() 可以。

1.4 信号的响应过程(重点)

信号从收到到响应有一个不可避免的延迟。在从 kernel 返回到 user 态的时候才会查看 mask 和 pending 位图的按位与,然后响应信号。

如何忽略掉一个信号的?(mask 清 0)

标准信号为什么要丢失(多次置 pending 为 1,只响应一次)。

在收到多个标准信号时,标准信号的响应没有严格的顺序。

在响应信号的时候,mask 置 0,防止重入。

1.5 信号相关函数(kill、raise、alarm、pause、abort)

1. kill(2) 系统调用可以发送任意信号给任意进程或进程组。

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

pid 有如下四种情况: 

  • 当 pid 为正数时,sig 信号被发送给 pid 指定的进程。
  • 当 pid 为 0 时,sig 信号会被发送给调用进程的进程组内的所有进程。
  • 当 pid 为 -1 时,sig 信号会被发送给当前进程有权限发送信号的每一个进程,除了 init 进程(1 号进程)。
  • 当 pid 小于 -1 时,sig 信号会被发送给 pgid 为 -pid 的进程组内的所有进程。

sig 参数为 0 时,不发送任何信号,可以用于检测进程和进程组是否存在(错误码为 ESRCH)。

2. raise(3) 可以给当前进程或线程发送信号。

#include <signal.h>

int raise(int sig);

在单线程的程序中 raise() 等效于: 

kill(getpid(), sig);

在多线程的程序中 raise() 等效于:

pthread_kill(pthread_self(), sig);

3. alarm(2) 系统调用可以定时发送一个 SIGALRM 信号(注意不要在一个程序中多次使用 alarm ,多次使用时,只有最后一个 alarm 生效)。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

当 seconds 为 0 时,所有等待的 alarm 都被取消。

alarm 可以用于实现流量控制,有如下两种方式:

  • 漏桶,就算海量的数据到来,还是以固定的速率处理数据,但没有数据的时候会死等。
  • 令牌桶, 没有数据的时候会攒令牌,当数据到来的时候可以根据令牌数量处理更多的数据。

例子,mytbf,可以使用令牌桶来读取文件内容:

/* main.c */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include "mytbf.h"

#define CPS 10
#define BUFSIZE 1024
#define BURST 100

int main(int argc, char **argv)
{
  int sfd, dfd = 1;
  char buf[BUFSIZE];
  int len, ret, pos, token_nums;
  mytbf_t *tbf;

  if (argc < 2) {
    fprintf(stderr, "Usage...\n");
    exit(1);
  }

  tbf = mytbf_init(CPS, BURST);
  if (tbf == NULL) {
    fprintf(stderr, "tbf is NULL\n");
    exit(1);
  }

  do {
    if ((sfd = open(argv[1], O_RDONLY)) < 0) {
      if (errno != EINTR) {
        perror("open()");
        exit(1);
      }
    }
  } while (sfd < 0);

  while (1) {
    token_nums = mytbf_
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值