Linux系统编程:进程part_2(信号相关)

前言

这一节内容我没怎么认真写,就是纯当草草过了一遍,这部分不是很重要当然能掌握肯定更好。
更多的是有个印象然后知道遇到这样的问题能回想起来知道怎么解决即可(虽然不太可能遇到)。

信号量

Linux系统编程:进程part_2(信号相关)_第1张图片

实现PV操作

P:测试并加锁,sem <= 0 会阻塞,但如果sem > 0的话 就 --sem

V:解锁,即++sem

实现PV操作可以分为几步,首先定义PV操作然后是调用PV操作。

定义PV操作需要用到的系统调用为:
Linux系统编程:进程part_2(信号相关)_第2张图片
对该系统调用的一些解释:
Linux系统编程:进程part_2(信号相关)_第3张图片
信号量的简单使用:
Linux系统编程:进程part_2(信号相关)_第4张图片
SEM_UNDO的作用是在进程终止的时候,把减去的资源给加回来,这个在下面的生产者消费者问题中有所体现。

生产者消费者问题

在考研课程还有什么哲学家进餐问题乱七八糟的,这些只对考研有用,在实际工作生产中,只要懂消费者生产者问题即可,所以这个很重要,利用之前学过的知识很容易写出这样的代码:

#include <43func.h>
int main()
{
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
    ERROR_CHECK(shmid, -1, "shmget");
    int *p = (int *)shmat(shmid, NULL, 0);
    ERROR_CHECK(p, (void *)-1, "shmat");
    p[0] = 10; // p[0] 表示仓库的个数
    p[1] = 0;  // p[1] 表示商品的个数
    int semid = semget(1000, 1, IPC_CREAT | 0600);
    ERROR_CHECK(semid, -1, "semget");
    int ret = semctl(semid, 0, SETVAL, 1);
    ERROR_CHECK(ret, -1, "semctl SETVAL");
    ret = semctl(semid, 0, GETVAL);
    ERROR_CHECK(ret, -1, "semctl GETVAL");
    printf("semval = %d\n", ret);
    struct sembuf P, V;
    P.sem_num = 0; //下标
    P.sem_op = -1; //对资源的影响
    P.sem_flg = SEM_UNDO;
    V.sem_num = 0;
    V.sem_op = 1;
    V.sem_flg = SEM_UNDO;
    if (fork() == 0)
    {
        while (1)
        {
            semop(semid, &P, 1);
            if (p[0] > 0)
            {
                printf("before produce, space = %2d, good = %2d, total = %d\n", p[0], p[1], p[0] + p[1]);
                --p[0];
                ++p[1];
                printf("after produce, space = %2d, good = %2d, total = %d\n", p[0], p[1], p[0] + p[1]);
            }
            semop(semid, &V, 1);
            // sleep(1);
        }
    }
    else if (fork() == 0)
    {
        while (1)
        {
            semop(semid, &P, 1);
            if (p[0] > 0)
            {
                printf("before produce, space = %2d, good = %2d, total = %d\n", p[0], p[1], p[0] + p[1]);
                --p[0];
                ++p[1];
                printf("after produce, space = %2d, good = %2d, total = %d\n", p[0], p[1], p[0] + p[1]);
            }
            semop(semid, &V, 1);
            // sleep(1);
        }
    }
    else
    {
        while (1)
        {
            semop(semid, &P, 1);
            if (p[1] > 0)
            {
                printf("before consume , space = %2d, good = %2d, total = %d\n", p[0], p[1], p[0] + p[1]);
                --p[1];
                ++p[0];
                printf("after consume, space = %2d, good = %2d, total = %d\n", p[0], p[1], p[0] + p[1]);
            }
            semop(semid, &V, 1);
            // usleep(100000);
        }
        wait(NULL);
    }
    shmdt(p);
    shmctl(shmid, IPC_RMID, NULL);
}

写这种PV操作时一定要分析清楚临界资源有哪些,一定要把所有对共享资源的访问保护到。

但是还是老话,实际工作当中我们不会用这样的方式,会用一些更加高效的手段,这后面再说。

消息队列

所谓消息队列其实有两种,我们这里说的是在进程间通信的消息队列,是一种IPC机制,而另一种消息队列是广义消息队列,它是一种网络通信的中间件,比如RacketMq之类的。

这里说的消息队列和之前说的管道其实非常相似,区别在于消息队列可以保留消息的边界。

什么意思?

先来说管道没有保留消息边界的含义:
比如我们用write系统调用写了两句消息,“你好”,“坏人”,通过管道传输给另一端,因为管道是流式消息,它根据先进先出原则在管道另一头的消息是:“人”“坏”“好”“你”,在管道另一边我们可以选择直接全部取出来也可以选择只读几个字,假如读三个字的话:“你好坏”,最后只剩个“人”了,消息含义就变了,
这就是由于管道没有保留消息边界所造成的:
Linux系统编程:进程part_2(信号相关)_第5张图片
后面学习网络编程时TCP也是流式传送数据的。

消息队列的做法则不同,还是以上面说的为例,发送方发送你好坏人,每一条消息都会以数据包的形式发送,会一个包一个包的发送给另一端,另一端自然只能一个包一个包的接收,这样就可以保留消息边界:
Linux系统编程:进程part_2(信号相关)_第6张图片
后面学习网络编程时UDP也是按数据包的形式传送的。

我们通过msgget系统调用来创建消息队列,注意介绍的依然是System V标准的系统调用,因为Posix标准的太难用:
Linux系统编程:进程part_2(信号相关)_第7张图片
对该系统调用的一些解析(注意消息队列是先进先出的):
Linux系统编程:进程part_2(信号相关)_第8张图片
代码简单测试:
发送数据的程序:

#include <43func.h>
typedef struct msgbuf{
    long mtype;
    char mtext[256];
} myMsg_t;
int main(){
    int msqid = msgget(1000,IPC_CREAT|0600);
    ERROR_CHECK(msqid,-1,"msgget");
    myMsg_t msg1;//Huangxiaoming
    myMsg_t msg2;//Wuyifan
    myMsg_t msg3;//Caixukun
    msg1.mtype = 1;
    strcpy(msg1.mtext,"Ganenguoqusuoyou,weilairenshijiaren");
    msg2.mtype = 2;
    strcpy(msg2.mtext,"skr skr~");
    msg3.mtype = 3;
    strcpy(msg3.mtext,"jinitaimei");
    msgsnd(msqid,&msg1,strlen(msg1.mtext), 0);
    msgsnd(msqid,&msg2,strlen(msg2.mtext), 0);
    msgsnd(msqid,&msg3,strlen(msg3.mtext), 0);
    puts("send over");

}

接收数据的程序:

#include <43func.h>
typedef struct msgbuf{
    long mtype;
    char mtext[256];
} myMsg_t;
int main(){
    int msqid = msgget(1000,IPC_CREAT|0600);
    ERROR_CHECK(msqid,-1,"msgget");
    long type;
    printf("who are you? 1 huangxiaoming 2 wuyifan 3 caixukun\n");
    scanf("%ld",&type);
    myMsg_t msg;
    memset(&msg,0,sizeof(msg));
    //msgrcv(msqid,&msg,sizeof(msg.mtext),type,0);
    //msgrcv(msqid,&msg,sizeof(msg.mtext),0,0);
    int ret = msgrcv(msqid,&msg,sizeof(msg.mtext),0,IPC_NOWAIT);
    ERROR_CHECK(ret,-1,"msgrcv");
    printf("you are %ld, msg = %s\n", type, msg.mtext);
}

proc文件系统

我们来介绍一下这个东西,我们在根目录下打开可以看见该文件目录,cd进行展示会发现很多文件大小都是0:
Linux系统编程:进程part_2(信号相关)_第9张图片
这是因为proc文件系统不是一个真正的磁盘系统,而是一个伪文件系统。

proc是process的缩写,所以proc文件系统的内容是操作系统的运行状态在文件系统中的映射。

这样做的好处是可以像修改文件一样去修改操作系统的属性。

proc文件目录下面的数字开头的内容其实对应的就是OS中的每一个进程,数字代表进程的PID号。

在这里插入图片描述
我们还可以进入这个sys文件目录下,即系统进程下去查看很多信息:
Linux系统编程:进程part_2(信号相关)_第10张图片
这里面有个kernel内核信息,我们可以在内核文件夹里去修改很多的内容,比如改共享内存的内容啊改消息队列的内容啊之类的。

信号

信号是一种软件层面的异步事件机制。

信号可能是进程发给进程的,也有可能是操作系统发给进程的。

而与之对应的在硬件层面的异步事件机制是中断。

使用man 7 signal命令查看信号手册:
Linux系统编程:进程part_2(信号相关)_第11张图片

信号默认行为

Linux系统编程:进程part_2(信号相关)_第12张图片

从上到下分别是:终止、忽略、终止并生成core、暂停、恢复。

Linux系统编程:进程part_2(信号相关)_第13张图片
可以看见上面的每个信号都会对应一个默认的行为。

我们学习这一节的内容,就是为了实现更改默认的信号行为,所以上面的内容只是铺垫。

信号产生的时机

Linux系统编程:进程part_2(信号相关)_第14张图片
进程或者操作系统或者硬件产生信号,然后递送到目标进程,中间会有一个传送的时间间隔,我们接下来的事情不会去管产生信号的行为,而是去修改传送信号的时间以及目标进程递送信号的行为。

当信号产生时

Linux系统编程:进程part_2(信号相关)_第15张图片

更改默认的信号行为

现在我们试图让信号递送时不再执行默认操作,而是调用一个函数。

这里我们可以用signal系统调用看看,用man 2 signal命令查看其man手册:
Linux系统编程:进程part_2(信号相关)_第16张图片
这个系统调用的作用是用来注册一个信号处理行为,注册的含义是等到信号到来时才会调用。
简单使用代码测试一下:
Linux系统编程:进程part_2(信号相关)_第17张图片

低速系统调用

低速系统调用是指可能陷入永久等待的系统调用。

Linux系统编程:进程part_2(信号相关)_第18张图片
signal特点之一就是一次注册,永久生效,如何改变这种永久生效的效果为让注册只生效一次?
可以使用SIG_DFL,设置其为默认的行为模式:
Linux系统编程:进程part_2(信号相关)_第19张图片
特点之二时,递送A时,会将A假如mask,其它信号不会加入mask,且会自动重启低速系统调用。

为了更好的控制上述特点,我们可以使用sigaction来替代signal:
Linux系统编程:进程part_2(信号相关)_第20张图片
虽然sigaction可以更好的控制信号,但是也更加复杂了,主要体现在其参数结构体的设计上:
Linux系统编程:进程part_2(信号相关)_第21张图片
简单测试:

#include <43func.h>
void sigFunc(int num){
    printf("before, num = %d\n", num);
    sleep(3);
    printf("after, num = %d\n", num);
}
void sigFunc3(int num, siginfo_t *siginfo , void * p){
    printf("num = %d\n", num);
    printf("sender pid = %d\n", siginfo->si_pid);
}
int main(){
    struct sigaction act;
    memset(&act,0,sizeof(act));
    act.sa_handler = sigFunc;
    //act.sa_sigaction = sigFunc3;
    //act.sa_flags = SA_RESTART|SA_SIGINFO|SA_RESETHAND;
    //act.sa_flags = SA_RESTART|SA_NODEFER;
    act.sa_flags = SA_RESTART;
    sigaddset(&act.sa_mask,SIGQUIT);
    int ret = sigaction(SIGINT,&act,NULL);
    ERROR_CHECK(ret,-1,"sigaction");
    //ret = sigaction(SIGQUIT,&act,NULL);
    //ERROR_CHECK(ret,-1,"sigaction");
    char buf[100] = {0};
    read(STDIN_FILENO,buf,sizeof(buf));
    printf("buf = %s\n", buf);
    //while (1)
    //{
    //}
    
}

sa_mask

Linux系统编程:进程part_2(信号相关)_第22张图片

sa_mask用来指定递送过程中的额外屏蔽信号

Linux系统编程:进程part_2(信号相关)_第23张图片

sigprocmask实现全程阻塞

Linux系统编程:进程part_2(信号相关)_第24张图片
简单测试:
Linux系统编程:进程part_2(信号相关)_第25张图片
运行结果:
Linux系统编程:进程part_2(信号相关)_第26张图片

获取pending集合

Linux系统编程:进程part_2(信号相关)_第27张图片

pause等待信号

Linux系统编程:进程part_2(信号相关)_第28张图片

kill发送信号

Linux系统编程:进程part_2(信号相关)_第29张图片

简单代码测试:
Linux系统编程:进程part_2(信号相关)_第30张图片
运行结果:
Linux系统编程:进程part_2(信号相关)_第31张图片
使用raise系统调用可以给自己发信号:
在这里插入图片描述
简单的代码测试:
Linux系统编程:进程part_2(信号相关)_第32张图片
运行效果:
Linux系统编程:进程part_2(信号相关)_第33张图片
在实现有序退出时该函数还是有用的。

alarm系统调用:定闹钟

Linux系统编程:进程part_2(信号相关)_第34张图片

可以简单的使用一下:
Linux系统编程:进程part_2(信号相关)_第35张图片

时钟

在这里插入图片描述

Linux系统编程:进程part_2(信号相关)_第36张图片
简单使用:
Linux系统编程:进程part_2(信号相关)_第37张图片

你可能感兴趣的:(Linux系统以及网络编程,linux,运维,服务器)