系统编程——并发

信号实现并发

同步
异步/并发:何时发生?什么结果?都是未知的
异步时间的处理:

  • 查询法:不断询问事件的发生(适合高频率)
  • 通知法:事件发生的时候反馈(适合低频率)
  1. 信号是什么?概念
    信号的软件中断
    信号的响应有依赖于中断
    中断是个硬件概念
  2. void (*signal(*int signum,void (*func)(int)))(int); 信号的发出函数
    命令 kill -l
    1-34标准信号 34-…
    多数的作用都是终止+core (core文件)

ulimit -a 看一下core文件大小,默认是0,因为经常出现段错误,会产生很多

信号会打断阻塞的系统调用

#include <signal.h>

typedef void (*sighandler_t)(int); // 函数指针重名为sighandler_t
sighandler_t signal(int signum, sighandler_t handler);
  1. 信号的不可靠
    信号行为不可靠,执行现场是内核布置,第一次调用没有结束,第二次调用开始了,第二次结果会将第一次结果覆盖:localtime

  2. 可重入函数
    第一次调用没有结束,第二次调用开始了,但是不会出错,就是可重入
    所有的系统调用都是可重入的,一部分库函数也是可重入的(尤其是返回值是指针类型的函数),eg:memcpy拷贝内存空间 _r后缀的函数

  3. 信号的响应过程
    信号从收到到响应有一个不可避免的延迟
    信号如何忽略掉一个信号的?
    标准信号为什么要丢失?
    标准信号的响应,没有严格的顺序

在这里插入图片描述

答1:信号的忽略的做法,就是将umask的该位置设置成0,那么&之后永远都是0,不会被响应

答2:每次接收信号都是将pending的那个信号位置设置为1,如果连续来多个同一个信号,他们的作用都是将那一位设置为1,多个来也没有作用,这是标准信号丢失的原因

  1. 信号常用函数
  • kill(); kill - send a signal to a process
// 成功返回0 失败返回-1,修改errno
int kill(pid_t pid, int sig); // pid >0 发送给pid进程
// pid = 0  发送给同组每一个进程(组内广播)
// pid = -1 发送给当前进程有权限发送的 每一个进程(除了init)本质只有init会使用
// pid < -1 发送给|pid|组内的所有进程
//  sig为0 就是没有信号

  • raise();
 #include <signal.h>
int raise(int sig); // 给自己进程发信号
  • alarm(); // 可以实现每秒。。。即流量控制
    alarm函数的默认作用是到达时间后,调用该函数的进程终止,但是一般情况下调用进程都会捕获该信号,
    如果想捕捉SIGALRM信号,则必须在调用alarm之前安装该信号的处理程序,
    如果先调用alarm,然后在我们能够安装SIGALRM处理程序之前已经接到该信号,那么进程将终止(摘取自apue 第三版p268)

pause函数使调用进程挂起,直到捕捉到一个信号,继续执行,eg捕获一个ALRM信号

 #include <unistd.h>
unsigned int alarm(unsigned int seconds); // 计时为0时会发送alarm信号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    alarm(5); // 5s后发送 alarm信号,会杀死当前进程

    alarm(1);

    alarm(5);

    
    while (1)
    {
        /* code */
    }
    

    return 0;
}

实现一个cat

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

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

#define CPS 10
#define BUFFSIZE CPS

static volatile int loop = 0;

static void alrm_handler(int s)
{
    alarm(1); // 第一次执行的时候,发出下一次执行的时钟信号
    loop = 1;
}

int main(int argc, char ** argv)
{   
    if (argc < 2)
    {
        fprintf(stdout, "usage");
        exit(1);
    }

    signal(SIGALRM, alrm_handler); // 接收时钟信号,
    alarm(1); // 一秒钟发一个时钟信号 1s后会执行时钟处理函数

    int sfd = 1;
    int dfd = 1;
    do
    {
        // 信号检查
        sfd = open(argv[1], O_RDONLY);
        if (sfd < 0)
        {
            if (errno != EINTR)
            {
                strerror(errno);
                exit(1);
            }
        }
    } while (sfd < 0);
    

    // const int SIZE = 8192;
    char buf[BUFFSIZE];
    int count = 0;
    while (1)
    {
        // loop初始导致这里阻塞 1s之后
        while (!loop)
        {
            pause(); // loop == 0  当alrm信号到来时,会打断pause系统调用,再次查看循环条件
        }
        loop = 0;  // 死循环下次再到while的时候,又pause,等待时钟信号到来,再跳出执行读写
        int len = 0; // read 返回值
        int ret = 0; // write 返回值
        int pos = 0; // 写截止的位置
        while ((len = read(sfd, buf, BUFFSIZE)) < 0)
        {
            if (len < 0)
            {
                if (errno == EINTR)
                    continue;
                
                strerror(errno);
                break;
            }
        }
        if (len == 0)
        {
            break;
        }

        // 防止写入不足
        while (len > 0)
        {
            ret = write(dfd, buf + pos, len); // 读出的结果写入到目标文件中
            if (ret < 0)
            {
                if (errno == EINTR)
                    continue;
                printf("%s\n", strerror(errno));
                exit(1);
            }
            pos += ret;
            len -= ret;
        }
        count++;
    }
    // printf("%d\n", count);
    close(sfd);
    return 0;
}

令牌桶实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

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

#define CPS 10
#define BUFFSIZE CPS
#define BURST 100
// 上限,最大积攒100个字节,每次是10个字节

// 令牌桶实例

static volatile sig_atomic_t token = 0; // 信号原子类型,这种类型的修改,机器会保证都是原子操作

static void alrm_handler(int s)
{
    alarm(1); // 第一次执行的时候,发出下一次执行的时钟信号
    token++;
    if (token > BURST)
    {
        token = BURST;
    }
}

int main(int argc, char ** argv)
{   
    if (argc < 2)
    {
        fprintf(stdout, "usage");
        exit(1);
    }

    signal(SIGALRM, alrm_handler); // 接收时钟信号
    alarm(1); // 一秒钟发一个时钟信号 1s后会执行时钟处理函数  发出第一个alrm信号

    int sfd = 1;
    int dfd = 1;
    do
    {
        // 信号检查
        sfd = open(argv[1], O_RDONLY);
        if (sfd < 0)
        {
            if (errno != EINTR)
            {
                strerror(errno);
                exit(1);
            }
        }
    } while (sfd < 0);
    

    // const int SIZE = 8192;
    char buf[BUFFSIZE];
    int count = 0;
    while (1)
    {
        // token初始导致这里阻塞 1s之后
        while (token <= 0) // 没有字节就等着
        {
            pause(); // 等待有信号打断pause 1s之后打断
        }
        // 累加
        token--;  // 执行一次 , 读10 写10 消耗一个令牌
        int len = 0; // read 返回值
        int ret = 0; // write 返回值
        int pos = 0; // 写截止的位置
        while ((len = read(sfd, buf, BUFFSIZE)) < 0) // token的作用在这里,如果时钟信号打断,token会增加到23445...积攒处理次数
        {
            if (len < 0)
            {
                if (errno == EINTR)
                    continue;
                
                strerror(errno);
                break;
            }
        }
        if (len == 0)
        {
            break;
        }

        // 防止写入不足
        while (len > 0)
        {
            ret = write(dfd, buf + pos, len); // 读出的结果写入到目标文件中
            if (ret < 0)
            {
                if (errno == EINTR)
                    continue;
                printf("%s\n", strerror(errno));
                exit(1);
            }
            pos += ret;
            len -= ret;
        }
        count++;
    }
    // printf("%d\n", count);
    close(sfd);
    return 0;
}

使用单一计时器,构造一组函数,实现任意数量的计时器

  • abort();
  • system();
  • sleep();
  1. 信号集
  2. 信号屏蔽字/判定集的处理
  3. 扩展
  • sigsuspend();
#include <signal.h>
int sigsuspend(const sigset_t *mask);

信号驱动程序

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

static void int_handler(int s)
{
    write(1, "!", 1);// 终端输出一个!
}

int main()
{
    sigset_t set, oset, saveset;
    signal(SIGINT, int_handler); // 发出中断信号的时候,打印一个!

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

    sigprocmask(SIG_UNBLOCK, &set, &saveset); // 保存当前场景的做法,一般使用UNBLOCK,把set中的信号unblock,把之前的状态存到saveset中
    sigprocmask(SIG_BLOCK, &set, &oset); // 保存当前场景的做法,一般使用UNBLOCK,把set中的信号unblock,把之前的状态存到saveset中
    for (int j = 0; j < 1000; j++)
    {
        // sigprocmask(SIG_BLOCK, &set, &oset); // 对信号集合中的信号进行屏蔽,集合中只有中断信号,保存当前信号集到oset
        sigprocmask(SIG_BLOCK, &set, NULL); 

        // 执行不受到影响---不响应
        for (int i = 0; i < 5; i++)
        {
            write(1, "*", 1); // 1的fd1
            sleep(1);
        }
        write(1, "\n", 1); // 1的fd1
        sigsuspend(&oset); // 一旦解除oset信号集阻塞,立即进入等待状态
        // sigset_t tmpset;
        // sigprocmask(SIG_SETMASK, &oset, &tmpset);
        // pause();
        // sigprocmask(SIG_SETMASK, &tmpset, NULL);
    }
    sigprocmask(SIG_SETMASK, &saveset, NULL); // 与保存状态方法互补,恢复saveset之前的状态
    return 0;
}

  • sigaction();
 #include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

// 对signum信号定义新的行为 sigaction,保存旧的信号行为oladact不保存也可以为空


 struct sigaction {
     void     (*sa_handler)(int); // 信号处理函数
     void     (*sa_sigaction)(int, siginfo_t *, void *); // 
     sigset_t   sa_mask; // 在响应信号的时候,将一个集合的信号阻塞住
     int        sa_flags;
     void     (*sa_restorer)(void);
};



// siginfo_t 

 siginfo_t {
	    int      si_signo;     /* Signal number */
	    int      si_errno;     /* An errno value */
	    int      si_code;      /* Signal code */
	    // 常用
	    ...
}

流量控制,仅内核信号可中断,使用sigaction,setitimer函数重构

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>

#include "mytbf.h"

// 结构体隐藏到.c中编译成库
struct mytbf_st
{
    int cps;
    int burst;
    int token;
    int pos;
};

// typedef void (*sighandler_t)(int);  // 标准中抽象出来是,要实现重命名
// static sighandler_t alrm_handler_save;
static int inited = 0;

static struct mytbf_st* job[MTBF_MAX];

void static alrm_action(int s, siginfo_t* infop, void* used)
{
    // 仅仅响应内核信号
    if (infop->si_code != SI_KERNEL)
    {
        return;
    }
    // alarm(1);
    for (int i = 0; i < MTBF_MAX; i++)
    {
        // 有任务存在
        if (job[i] != NULL)
        {
            job[i]->token += job[i]->cps;
            if (job[i]->token < job[i]->burst)
            {
                job[i]->token = job[i]->burst;
            }
        }
    }
}

static struct sigaction alrm_sa_save;

static void module_unload(void)
{
    // signal(SIGALRM, alrm_handler_save); // 恢复
    // alarm(0);

    struct itimerval itv;

    sigaction(SIGALRM, &alrm_sa_save, NULL); // 恢复

    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 0;
    itv.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL, &itv,NULL);

    for (int i = 0; i < MTBF_MAX; i++)
    {
        free(job[i]);
    }
}

static void module_load()
{
    // alrm_handler_save = signal(SIGALRM, alrm_handler); // 被修改的信号返回
    // alarm(1);
    struct sigaction sa;
    struct itimerval itv;

    sa.sa_sigaction = alrm_action;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO; // 第三个参数,表示使用的是三参的处理函数
    
    if (sigaction(SIGALRM,&sa, &alrm_sa_save) < 0) // 第三个参数是保存
    {
        perror("");
        exit(2);
    }


    itv.it_interval.tv_sec = 1;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 1;
    itv.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &itv, NULL);
    atexit(module_unload); // 钩子函数
}

static int min(int a, int b)
{
    return a < b ? a : b;
}

static int get_free_pos(void)
{
    for (int i = 0; i < MTBF_MAX; i++)
    {
        if (job[i] == NULL)
        {
            return i;
        }
    }
    return -1;
}

mytbf_t* mytbf_init(int cps, int burst)
{
    struct mytbf_st *me;
    if (inited == 0)
    {
        module_load();
        inited = 1;
    }

    int pos = get_free_pos();
    if (pos < 0)
    {
        // 没有空位了
        return NULL;
    }
    me = malloc(sizeof(*me));
    if (me == NULL)
    {
        return NULL;
    }
    me->token = 0;
    me->burst =  burst;
    me->cps = cps;
    me->pos = pos;
    job[pos] = me; // 把一个结构体指针存放入数组
    return me;
}

int mytbf_fetchtoken(mytbf_t* ptr, int size)
{
    struct mytbf_st *me = ptr;

    if (size <= 0)
        return -1;

    while (me->token <= 0)
        pause();

    int n = min(me->token, size);
    me->token -= n;
    return n;
}

int mytbf_returntoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me = ptr;

    if (size <= 0)
        return -1;

    me->token += size;

    if (me->token > me->burst)
        me->token = me->burst;

    return size;
}

int mytbf_destory(mytbf_t *ptr)
{
    struct mytbf_st *me = ptr;
    job[me->pos] = NULL;
    free(ptr);
}

重构守护进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>


#define FNAME "/tmp/out"

static int daemonize(void)
{
    FILE* fd;
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork");
        exit(1);
    }
    else if (pid > 0)
    {
        exit(0); // 父进程退出
    }
    else
    {
        // 脱离终端 012需要重定向  不能定向到终端
        fd = open("dev/null", O_RDWR);
        if (fd < 0)
        {
            perror("open");
            return (-1);
        }
        // 守护进程打开文件,使其具有文件描述符012, 任何一个读写err的库例程都不会产生效果
        dup2(fd, 0);  // 文件输入输出重定向
        dup2(fd, 1);
        dup2(fd, 2);
        if (fd < 2)
            close(fd);

        setsid(); // 子进程设置为守护进程 
        chdir("/"); // 工作路径设置到根目录 因为从父进程继承来的工作目录可能是个挂载的目录,
        // 如果守护进程的工作目录也是一个挂载目录,那么该文件系统将不可以被卸载
        // umask(0);
        return 0;
    }
}

static FILE* fp;
static int daemon_exit(int s)
{
    // 此信号处理函数是可重入的,就是在处理一个信号的时候,另一个信号也来了(通过底层的&运算),
    // 那么此时如果刚刚执行fclose,没有closelog的执行,响应了另一个信号,再次进入到该信号处理函数中,
    // 解决方法:在进入信号处理函数的时候,将其他信号屏蔽,处理结束的时候,在解除屏蔽sigprocmask
    fclose(fp);
    closelog();
    exit(1);
}

int main()
{
    // 必须使用规定信号才可以杀死该进程

    // signal(SIGINT,daemon_exit);
    // signal(SIGQUIT,daemon_exit);
    // signal(SIGTREM,daemon_exit);
    struct sigaction sa;
    sa.sa_handler = daemon_exit; // 第一个参数 第二个是三个参数的信号处理函数,暂时不考虑
    sigemptyset(&sa.sa_mask); // 第三个参数  执行处理函数期间,被屏蔽的信号集
    sigaddset(&sa.sa_mask, SIGQUIT);
    sigaddset(&sa.sa_mask, SIGTERM);
    sigaddset(&sa.sa_mask, SIGINT);  // 响应SIGINT的时候,SIGINT的mask位是0,再将其加入到屏蔽集合中,只不过是再次的设置一次0
    sa.sa_flags = 0; // 第四个参数,一般用不到,先设置为0

    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL); // 三个信号驱动,这三个信号都可以实现阻塞进程
    sigaction(SIGQUIT, &sa, NULL);
    //      一般使用pid   守护进程类别
    openlog("mydaemon", LOG_PID, LOG_DAEMON);
    // 守护进程
    if (daemonize())
    {
        syslog(LOG_ERR, "init failed");
    }
    else
    {
        syslog(LOG_INFO, "successed");
    }

    // 守护进程做的任务
    fp = fopen(FNAME, "w+");
    if (fp == NULL)
    {
        syslog(LOG_ERR, "write file failed");
        exit(1);
    }

    syslog(LOG_INFO, "%s opened", FNAME);
    for (int i = 0; ;i++)
    {
        fprintf(fp, "%d", i);
        fflush(NULL);
        syslog(LOG_INFO, "%d wrote", i);
        sleep(1);
    }
    closelog();   // 执行不到,因为都是异常终止
    fclose(fp);

    return 0;
}
  • setitimer();
    更精确的时间控制时间
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

 struct itimerval {
      struct timeval it_interval; /* Interval for periodic timer */
       struct timeval it_value;    /* Time until next expiration */
   };

   struct timeval {
       time_t      tv_sec;         /* seconds */
       suseconds_t tv_usec;        /* microseconds */
   };
  1. 实时信号
    解决标准信号的不足,响应有顺序,同时到来是先响应标准信号的

多线程实现并发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值