Linux system programming: process part_2 (signal related)

Foreword

I didn’t write this section seriously, I just went through it hastily. This part is not very important, but it will definitely be better if you can master it.
It’s more about having an impression and then knowing that if you encounter such a problem, you can think back and know how to solve it (although it is unlikely to encounter it).

Semaphore

Implement PV operation

P: Test and lock, sem <= 0 will block, but if sem > 0, just –sem

V: unlock, that is + + sem

Implementing PV operations can be divided into several steps. First, define the PV operation and then call the PV operation.

The system calls required to define PV operations are:

Some explanations of this system call:

Simple use of semaphores:

The function of SEM_UNDO is to add back the subtracted resources when the process terminates. This is reflected in the producer-consumer problem below.

Producer-consumer problem

Are there any messy questions about philosophers dining in the postgraduate entrance examination courses? These are only useful for the postgraduate entrance examination. In actual work and production, you only need to understand the consumer-producer problem, so this is very important. It is easy to write it using the knowledge you have learned before. Code like this:

#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] represents the number of warehouses
    p[1] = 0; // p[1] represents the number of items
    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\\
", ret);
    struct sembuf P, V;
    P.sem_num = 0; //Subscript
    P.sem_op = -1; //Impact on resources
    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 = -, good = -, total = %d\\
", p[0], p[1], p[0] + p[1]);
                --p[0];
                 + + p[1];
                printf("after produce, space = -, good = -, total = %d\\
", 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 = -, good = -, total = %d\\
", p[0], p[1], p[0] + p[1]);
                --p[0];
                 + + p[1];
                printf("after produce, space = -, good = -, total = %d\\
", 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 = -, good = -, total = %d\\
", p[0], p[1], p[0] + p[1]);
                --p[1];
                 + + p[0];
                printf("after consume, space = -, good = -, total = %d\\
", p[0], p[1], p[0] + p[1]);
            }
            semop(semid, &V, 1);
            // usleep(100000);
        }
        wait(NULL);
    }
    shmdt(p);
    shmctl(shmid, IPC_RMID, NULL);
}

When writing this kind of PV operation, you must analyze clearly what critical resources are and protect all access to shared resources.

But as the old saying goes, we will not use this method in actual work, but will use more efficient methods, which will be discussed later.

Message Queue

There are actually two types of message queues. What we are talking about here is the message queue that communicates between processes, which is an IPC mechanism. The other message queue is the generalized message queue, which is a kind of middleware for network communication, such as RacketMq. some type of.

The message queue mentioned here is actually very similar to the pipeline mentioned before. The difference is that the message queue can retain the boundaries of the message.

What’s the meaning?

First let’s talk about the meaning of pipes not retaining message boundaries:
For example, we use the write system call to write two messages, “Hello” and “Bad Guy”, and transmit them to the other end through the pipeline. Because the pipeline is a streaming message, the message at the other end of the pipeline based on the first-in, first-out principle is: ” “People”, “bad”, “good” and “you”, on the other side of the pipe we can choose to take them all out directly or we can choose to read only a few words. If we read three words: “you are good or bad”, in the end only ” “person”, the meaning of the message changes.
This is caused by the pipe not preserving message boundaries:

When learning network programming later, TCP also streams data.

The approach of the message queue is different. Let’s take the above example as an example. When the sender sends you a good or bad message, each message will be sent in the form of a data packet. One packet will be sent to the other end. Naturally, the other end can only send one message. Wrap the reception of a packet so that message boundaries are preserved:

When learning network programming later, UDP is also transmitted in the form of data packets.

We create the message queue through the msgget system call. Note that the introduction is still the System V standard system call, because the Posix standard is too difficult to use:

Some analysis of this system call (note that the message queue is first in, first out):

Simple code test:
Program to send data:

#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, & amp;msg1,strlen(msg1.mtext), 0);
    msgsnd(msqid, & amp;msg2,strlen(msg2.mtext), 0);
    msgsnd(msqid, & amp;msg3,strlen(msg3.mtext), 0);
    puts("send over");

}

Program to receive data:

#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\\
");
    scanf("%ld", & amp;type);
    myMsg_t msg;
    memset( & amp;msg,0,sizeof(msg));
    //msgrcv(msqid, & amp;msg,sizeof(msg.mtext),type,0);
    //msgrcv(msqid, & amp;msg,sizeof(msg.mtext),0,0);
    int ret = msgrcv(msqid, & amp;msg,sizeof(msg.mtext),0,IPC_NOWAIT);
    ERROR_CHECK(ret,-1,"msgrcv");
    printf("you are %ld, msg = %s\\
", type, msg.mtext);
}

proc file system

Let’s introduce this thing. When we open it in the root directory, we can see the file directory. If we display it with cd, we will find that the size of many files is 0:

This is because the proc file system is not a real disk system, but a pseudo file system.

proc is the abbreviation of process, so the content of the proc file system is the mapping of the operating status of the operating system in the file system.

The advantage of this is that you can modify the properties of the operating system just like modifying files.

The content starting with a number under the proc file directory actually corresponds to each process in the OS, and the number represents the PID number of the process.


We can also enter the sys file directory, that is, the system process, to view a lot of information:

There is kernel information here. We can modify a lot of content in the kernel folder, such as changing the contents of shared memory, changing the contents of the message queue, and so on.

Signal

Signals are a software-level asynchronous event mechanism.

The signal may be sent by a process to a process, or it may be sent by the operating system to a process.

The corresponding asynchronous event mechanism at the hardware level is interrupt.

Use the man 7 signal command to view the signal manual:

Signal default behavior

From top to bottom they are: terminate, ignore, terminate and generate core, pause, and resume.


You can see that each signal above corresponds to a default behavior.

We study the content of this section just to change the default signal behavior, so the above content is just a foreshadowing.

Timing of signal generation


The process or operating system or hardware generates a signal and then delivers it to the target process. There will be a transmission time interval in the middle. Our next thing will not be about the behavior of generating the signal, but to modify the time of transmitting the signal and the delivery of the target process. signaling behavior.

When the signal is generated

Change default signal behavior

Now we are trying to make the signal delivery not perform the default action, but call a function.

Here we can use the signal system call to take a look, and use the man 2 signal command to view its man manual:

The function of this system call is to register a signal processing behavior. Registration means that it will not be called until the signal arrives.
Simply use the code to test it:

Low-speed system calls

Slow system calls are system calls that may be stuck waiting forever.


One of the characteristics of signal is that once registered, it takes effect permanently. How to change this permanent effect so that the registration only takes effect once?
You can use SIG_DFL to set it as the default behavior mode:

The second feature is that when delivering A, A will be masked, other signals will not be added to the mask, and low-speed system calls will be automatically restarted.

In order to better control the above characteristics, we can use sigaction instead of signal:

Although sigaction can better control signals, it is also more complex, mainly reflected in the design of its parameter structure:

Simple test:

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

sa_mask

sa_mask is used to specify additional masking signals during delivery

sigprocmask implements full blocking


Simple test:

operation result:

Get pending collection

pause waiting for signal

kill sends signal

Simple code test:

operation result:

You can signal yourself using the raise system call:

Simple code test:

running result:

This function is still useful when implementing orderly exit.

alarm system call: set alarm clock

You can simply use:

Clock


Simple to use: