Important knowledge points of Linux

I Important function

1. fctnl() function

1.1 function description

fcntl system call can be used to perform various control operations on the opened file descriptor to change various properties of the opened file
Function prototype

#include<unistd.h>  
#include<fcntl.h>  
int fcntl(int fd, int cmd);  
int fcntl(int fd, int cmd, long arg);  
int fcntl(int fd, int cmd ,struct flock* lock);// File lock

The functions of fcntl function vary according to the value of cmd. The corresponding functions of parameters are as follows:

Set the lock structure when locking a file

(1)F_DUPFD

Like the dup function, copy the file descriptor pointed by fd, and return the new file descriptor after successful call, pointing to the same file together with the old file descriptor.

(2)F_GETFD

Read file descriptor close on exec flag

(3)F_SETFD

Set the file descriptor close on exec flag to the last bit of the third parameter arg

(4)F_GETFL

Get the flag of the file opening method. The meaning of the flag value is consistent with that of the open call

(5)F_SETF

Set the file opening method to arg

For file locks

1.2 practical use

Example 1: set file descriptor non blocking

int main(int argc, char *argv[])
{
    char buf[1024] = {0};
    int ret;

    int flags;
    //Get the status of fd first
    flags = fcntl(0, F_GETFL, 0);
    if (flags == -1)
        ERR_EXIT("fcntl get flag error");
    
    //Set the state of fd again, because if you get it directly, other states of previous fd will be removed
    ret = fcntl(0, F_SETTFL, flags | O_NONBLOCK);
    if (flags == -1)
        ERR_EXIT("fcntl set flag error");
    ret =read(0, buf, 1024);
    if (ret == -1)
        ERR_EXIT("read error");
    
    printf("buf=%s\n", buf);

    return 0;
}

Example 2: copy file descriptor (file status flag)

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    int fd1;

    fd = open("test.txt", O_RDONLY);
    if (fd == -1)
        ERR_EXIT("open error");

    // close(1);// fd=1 is turned off, so the value of the returned file descriptor is 1, so 1 and fd point to the same file
    // dup(fd);
    // //The two lines above are equivalent to dup2(fd,1);

    //fcntl emulation copy file descriptor
    close(1);//Turn off standard output because 0, 1 and 2 are turned on by default

    //Because fcntl is searched from 0, fd=1 is currently available
    if (fcntl(fd, F_DUPFD, 0) <0)
        ERR_EXIT("fcntl with error");
    

    printf("hello\n");//Output to standard output. Standard output has been relocated to test txt
    return 0;
}

Example 3: locking a file

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

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


int main(int argc, int argv*[])
{
    //To add a read lock to a file, you must have read permission to the file
    //To write lock a file, you must have write permission on the file    
    int fd;
    fd = open("test2.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1)
        ERR_EXIT("open error");
    struct flock lock;
    memset(&lock, 0, sizeof(lock));
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;//Reference position
    lock.l_start = 0;//Offset position
    lock.l_len = 0;//A value of 0 locks the entire file

    if (fcntl(fd, F_SETLK, &lock) == 0) // If it is locked, other processes will fail to lock again
    // If (fcntl (FD, f_setlkw, & lock) = = 0) / / this kind of locking will block the waiting when other processes try to lock again.
    {
        printf("lock success\n");
        printf("press any key to unlock\n");
        getchar();//press any key 
        lock.l_type = F_UNLCK;
        if (fcntl(fd, F_SETLK, &lock) == 0)
            printf("unlock success\n");
        else
            printf("unlock fail\n");
        
    }
    else
    {
        ERR_EXIT("lock fail\n");
    }

    //Even if it is not unlocked, the lock will be released when the process exits
    return 0;
    
}

1.3 file descriptor flag FD_CLOEXEC

Fork function: the child process obtains the data space, heap and stack copies of the parent process by copy on write (COW), which also includes file descriptors. When the fork just succeeds, the same file descriptor in the parent-child process points to the same entry in the system file table (which also means that they share the same file offset).

Then, generally, we will call exec to execute another program in the sub process. At this time, we will replace the body, data, heap and stack of the sub process with a new program. At this time, of course, the variable to save the file descriptor does not exist, so we can't close the useless file descriptor. Therefore, we usually fork the child process, directly execute close in the child process, close useless file descriptors, and then execute exec.

However, in complex systems, sometimes we don't know how many file descriptors (including socket handles, etc.) are opened when we fork the sub process, so it's really difficult to clean them one by one. What we expect is to specify when a file handle is opened before the fork subprocess: "this handle will be closed when I execute exec after the fork subprocess". In fact, there is such a method: the so-called close on exec.
for example

     int fd=open("foo.txt",O_RDONLY);
     int flags = fcntl(fd, F_GETFD);
     flags |= FD_CLOEXEC; // When the child process calls the exec function, the file descriptor is automatically closed
     fcntl(fd, F_SETFD, flags);

This option is also available in socket programming
For details, see the close on exec mechanism between linux processes_ Curtain roll westerly column - CSDN blog
https://blog.csdn.net/ljxfblog/article/details/41680115?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control

1.4 more references

For more information:

  1. Usage Summary of fcntl function - zxiaocheng - blog Garden
    https://www.cnblogs.com/zxc2man/p/7649240.html
  2. Linux fcntl function details - evening treatment - blog Park
    https://www.cnblogs.com/xuyh/p/3273082.html

2. mmap function

mmap() memory mapping function. Memory mapping, in short, is to map a memory area in user space to kernel space. After successful mapping, the user's modifications to this memory area can be directly reflected in kernel space. Similarly, the modifications to this area in kernel space can also directly reflect user space. Then, for kernel space < --- > user space, if a large number of data transmission and other operations are required between the two, the efficiency is very high.

The port parameter in the mmap function is used to set the access permission of the memory segment, and the flags parameter controls the behavior of the program after the content of the memory segment is modified.

In the project, the port of mmap is set to PORT_READ (memory segment readable), and the flags parameter is set to (MAP_PRIVATE), that is, the project only writes the file to the file descriptor, so set map_ Private, which means that the modifications to the memory segment will not be reflected in the mapped file.

2. Signal

2.1 signal related events and states

1. Generate signal

  1. Press key to generate, such as Ctrl+c, Ctrl+z and Ctrl+\
  2. Generated by system call, such as: kill (function), raise, abort
  3. Software condition generation, such as timer alarm
  4. Hardware exceptions occur, such as illegal access to memory (segment error), except 0 (floating point number exception), memory alignment error (bus error), for example:
    Except for 0 operation → 8) SIGFPE (floating point number exception) "F" ---- floating point number.
    Illegal access to memory → 11) SIGSEGV (segment error)
    Bus error → 7) SIGBUS
  5. Command generation, such as kill Command

2. Delivery: delivery and arrival process.
3. Pending: the state between generation and delivery. This state is mainly caused by blocking (shielding)
4. Signal processing mode:

1 perform default actions
2 ignore (discard)
3 capture (call user processing function)

The process control block PCB of Linux kernel is a structure, task_struct not only contains process id, status, working directory, user id, group id and file descriptor table, but also contains signal related information, mainly referring to blocking signal set and pending signal set.

5 blocking signal set (signal shielding word): add some signals to the set and set shielding for them. When the x signal is shielded and then received, the signal processing will be delayed (after the shielding is removed)

6. Pending signal set:

  1. When the signal is generated, the bits describing the signal in the pending signal set are immediately turned to 1, and the table signal is in the pending state. When the signal is processed, the corresponding bit is flipped
    Turn back to 0. This moment is often very short.
  2. After the signal is generated, it cannot arrive due to some reasons (mainly blocking). The set of such signals is called the set of pending signals. The signal is pending until the shielding is removed.

2.2 four elements of signal

1. No. 2 Name 3 Event 4 Default processing action

2.3 signal generation

2.3.1. Terminal button generates signal

Ctrl + c → 2) SIGINT "INT" ---- Interrupt
Ctrl + z → 20) SIGTSTP (pause / stop) "T" ---- Terminal.
Ctrl + \ → 3) SIGQUIT

2.3.2. Signal generated by abnormal hardware

Except for 0 operation → 8) SIGFPE (floating point number exception) "F" ---- floating point number.
Illegal access to memory → 11) SIGSEGV (segment error)
Bus error → 7) SIGBUS

2.3.3. The system calls functions / commands to generate signals (kill,abort,raise)

(1) The kill command generates a signal: kill -SIGKILL pid

(2) Kill function: send the specified signal to the specified process (not necessarily kill)
int kill(pid_t pid, int sig); Success: 0; Failure: - 1 (illegal ID, illegal signal, common user killing init process, etc.).

Exercise: create 5 child processes in a loop, and the parent process terminates any child process with the kill function

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#define N 5
int main(void)
{
    int i;
    pid_t pid, q;	
    for (i = 0; i < N; i++) 
	{
        pid = fork();
        if (pid == 0)
        {
			break;
        }	
        if (i == 2)
            q = pid;
    }
    if (i < 5) 
    {            //Subprocess

            printf("I'm child %d, getpid = %u\n", i, getpid());
            sleep(10);	
    } 
	else 
	{                //Parent process
        sleep(3);
        kill(q, SIGKILL);
        printf("------------kill %d child %u finish\n", 2, q);
        while (1);
    }	
    return 0;
}

(3) raise and abort functions

2.3.4 signal generated by software condition

(1) alarm function
Set the timer (alarm clock). After specifying seconds, the kernel will send SIGALRM signal No. 14 to the current process. When the process receives this signal, the default action is terminated.

Each process has and only has a unique timer.
unsigned int alarm(unsigned int seconds); Returns 0 or the number of seconds remaining, no failure.

Timing, independent of the process state (NATURAL timing method)! Ready, running, suspending (blocking, pausing), terminating, Zombie... The alarm is timed no matter what state the process is in.

Exercise: write a program to test how many computers you can count in one second.

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

int main(void)
{
	int i;
	alarm(1);
	for(i = 0; ; i++)
		printf("%d\n", i);
	return 0;
}

(2) setitimer function
Set the timer (alarm clock). It can replace the alarm function. Precision microseconds us, can achieve periodic timing.

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); Success: 0; Failed: - 1, setting
errno

Parameter: which: Specifies the timing method
① Natural timing: ITIMER_REAL → 14) sigarm calculates the natural time
② Virtual space timing (user space): ITIMER_VIRTUAL → 26) SIGVTALRM only calculates the cpu time occupied by the process
③ Runtime timing (user + kernel): ITIMER_PROF → 27) SIGPROF calculates the cpu occupation and system call execution time

Parameters: struct itimerval, as shown below, are interval time and first trigger time respectively
it.it_interval.tv_sec=0;// Interval events (seconds)
it.it_interval.tv_usec=0; (subtle)
it.it_value.tv_sec=sec;// First trigger time
it.it_value.tv_usec=0;

The structure can realize timing and trigger every interval after the first trigger
Exercise: test how many times your computer can count in one second (mode 2)

#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <stdio.h>
unsigned int my_alarm(unsigned int sec)
{
 struct itimerval it,oldit;
 int ret;
  it.it_interval.tv_sec=0;//Interval event, 0 seconds
  it.it_interval.tv_usec=0;
  it.it_value.tv_sec=sec;//First trigger time, 1 second
  it.it_value.tv_usec=0;
  ret=setitimer(ITIMER_REAL,&it,&oldit);
  if(ret==-1)
  perror("setitimer");
  return oldit.it_value.tv_sec;
}
int main(void)
{
 int i;
 my_alarm(1); //By default, signal No. 9 is transmitted, and the default function is to terminate the program 
 for(i=0;;i++)
 printf("%d\n",i);
 return 0; 

}


2.3.5 signal set operation function

The kernel reads the pending signal set to determine whether the signal should be processed. The signal mask word mask can affect the pending signal set. We can customize the set in the application to change the mask. The purpose of shielding the specified signal has been achieved. 1 in the signal shielding word indicates shielding

(1) Signal set setting function
sigset_t set; // typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); A signal is cleared successfully: 0; Failed: - 1
int sigfillset(sigset_t *set); Set a signal to 1 successfully: 0; Failed: - 1
int sigaddset(sigset_t *set, int signum); Adding a signal to the signal set succeeded: 0; Failed: - 1
int sigdelset(sigset_t *set, int signum); Successfully clear a signal out of the signal set: 0; Failed: - 1
int sigismember(const sigset_t *set, int signum); Judge whether a signal returns value in signal set: in set: 1; Not in: 0;
Error: - 1

(2) sigprocmask function
This function is also used to mask and de mask signals. Its essence is to read or modify the signal shielding word (in PCB) of the process.
Strictly note that the signal is shielded: only the signal processing is delayed (until the shielding is removed); And ignoring means that the signal is lost for processing.

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); Success: 0; Failed: - 1, setting errno

Set: the incoming parameter is a bitmap. The position 1 in the set indicates which signal the current process is shielding (that is, the signal set set by the signal setting function).
oldset: outgoing parameter, saving the old signal mask set.
how parameter value: it is assumed that the current signal mask word is mask

  1. SIG_BLOCK: when how is set to this value, set indicates the signal to be shielded. Equivalent to mask = mask|set
  2. SIG_UNBLOCK: when how is set to this, set indicates the signal that needs to be unshielded. Equivalent to mask = Mask & ~ set
  3. SIG_SETMASK: when how is set to this, set indicates the new mask set used to replace the original mask and. Equivalent to mask = set
    If calling sigprocmask unblocks the current signals, at least one of them will be blocked before sigprocmask returns
    A signal is delivered.

(3) sigpending function
Reads the pending semaphore set for the current process
int sigpending(sigset_t *set); set outgoing parameters. Return value: Success: 0; Failed: - 1, setting errno

Exercise: print the pending status of all conventional signals to the screen, and set the No. 3 signal shielding word and print it.

// When sending No. 3 signal at the terminal, it will be found that the value of No. 3 signal changes from 0 to 1, indicating that the blocking signal set is set through sigprocmask function to affect the pending signal set
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void printset(sigset_t *ped)//Print operation 
{
	int i;
	for(i = 1; i < 32; i++)
	{
		if((sigismember(ped, i) == 1))//Using member judgment function
		{
			putchar('1');
		} 
		else 
		{
			putchar('0');
		}
	}
	printf("\n");
}

int main(void)
{
	sigset_t set, oldset, ped; // Set signal mask word
	sigemptyset(&set);//empty
	sigaddset(&set, SIGINT);//Set blocking SIGINT
//SIG_BLOCK indicates adding a blocking signal, set indicates the signal set to be covered, and oldset indicates the old signal set
	sigprocmask(SIG_BLOCK, &set, &oldset); // This function affects the pending signal set

	while(1)
	{
		sigpending(&ped);       //Get pending semaphore set outgoing
		printset(&ped); //Print custom functions
		sleep(1);
	}

	return 0;
}

2.3.6 signal capture

(1) signal function
Register a signal capture function:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
This function is defined by ANSI and may behave differently in different versions of Unix and Linux for historical reasons. Therefore, every effort should be made
Avoid using it and use the sigaction function instead.

Exercise: capture the ctrl+c trigger event signal and modify its processing action

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
void do_sig(int a)
{
    printf("hello world!\n");
}
int main(void)
{      
    if (signal(SIGINT, do_sig)== SIG_ERR)
    {
        perror("signal");
        exit(1);
    }
    while (1) {
        printf("---------------------\n");
        sleep(1);
    }

    return 0;
}


(2) sigaction function
Modify the signal processing action (usually used to register a signal capture function in Linux)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); Success: 0; Failed: - 1, setting errno
Parameters:
act: pass in parameters, new processing method.

oldact: outgoing parameter, old processing method.

struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};

sa_restorer: this element is obsolete and should not be used. POSIX 1 the standard will not specify this element. (deprecated)
sa_sigaction: when sa_flags is specified as SA_ This signal handler is used when siginfo flag is. (rarely used)
Key points:
① sa_handler: Specifies the name of the processing function after signal capture (i.e. the registration function). It can also be assigned to SIG_IGN table ignored or SIG_DFL table execution
Recognition action
② sa_mask: the signal set (signal mask word) to be shielded when calling the signal processing function. Note: mask only when the handler function is called
Effective. It is a temporary setting.
③ sa_flags: usually set to 0, and the table uses the default attribute.

siaction processing features:

1 #include <signal.h>
  2 #include <stdio.h>
  3 #include <unistd.h>
  4 
  5 
  6 void ouch(int sig)
  7 {
  8     printf("oh, got a signal %d\n", sig);
  9 
 10     int i = 0;
 11     for (i = 0; i < 5; i++)
 12     {
 13         printf("signal func %d\n", i);
 14         sleep(1);
 15     }
 16 }
 17 
 18 
 19 int main()
 20 {
 21     struct sigaction act;
 22     act.sa_handler = ouch;
 23     sigemptyset(&act.sa_mask);
 24     sigaddset(&act.sa_mask, SIGQUIT);
 25     // act.sa_flags = SA_RESETHAND;
 26     // act.sa_flags = SA_NODEFER;
 27     act.sa_flags = 0;
 28 
 29     sigaction(SIGINT, &act, 0);
 30 
 31 
 32     struct sigaction act_2;
 33     act_2.sa_handler = ouch;
 34     sigemptyset(&act_2.sa_mask);
 35     act.sa_flags = 0;
 36     sigaction(SIGQUIT, &act_2, 0);
 37 
        while(1)
        {
             sleep(1);
        }
 38     return;
 
    }

  1. Blocking. The sigaction function has the function of blocking. For example, when a SIGINT signal comes, it enters the signal processing function. By default, if another SIGINT signal comes before the signal processing function is completed, it will be blocked. Only after the signal processing function is completed, the subsequent SIGINT will be processed again. At the same time, no matter how many sigints come later, Only one SIGINT is processed, and sigaction will queue and merge subsequent sigints.

  2. sa_mask, signal shielding set, can clear and add signals to be shielded through functions such as sigemptyset/sigaddset. In the above code, when processing signal SIGINT, if the signal SIGQUIT comes, it will be shielded. However, if SIGINT comes when processing SIGQUIT, it will be processed first and then SIGQUIT.

Keywords: Linux

Added by Liquix on Mon, 03 Jan 2022 08:19:11 +0200