Linux system programming 58 Advanced IO-select() of IO multiplexer Monitor the read, write, and abnormal status of file descriptors

Why is multiplexing introduced:
In the program where the state machine realizes copying data between two devices, you can find such a problem: During the running of the program, it must be in a busy state, and the CPU usage rate is very high. The reason for wasting CPU time is that the program is actually busy judging false errors, rereading and rewriting most of the time. This is an IO-intensive task that is not load-intensive, that is, the amount of data is not large, but the IO is very intensive. For IO-intensive tasks, the program can be multiplexed with IO. The essence is to monitor the behavior of the file descriptor. When the current file descriptor has a behavior that I am interested in, subsequent operations will be performed, such as the previous two For data exchange between two devices, the previous method is blind push, which is constantly detecting and trying to see if the content is readable or writable. If we use IO multiplexing, it can become that when a certain file descriptor state has an action that I am interested in, I will do subsequent operations to save CPU time.

IO multiplexing: monitor the behavior of file descriptors, fd is readable? Writable? Are there any abnormal conditions to be handled? When the file descriptor reaches the state we are interested in, the function returns.
select(), portable
poll(), portable
epoll(), Linux dialect based on poll

       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing

SYNOPSIS
       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

/* If the function does not implement the timeout setting, the function will block and wait until the event of interest occurs, that is, the file descriptor in the set of file descriptors that is concerned becomes readable and writable. The function will return only when it is in the state. Note that the three fd_sets are not const types, they are mutable. When select() returns, these three sets are no longer the supervisor site we arranged before, but the place where the results are stored. If select() does not set a timeout, the blocking waiting is interrupted by a signal, and a false error occurs, then these three sets will also be cleared.

nfds: the number of file descriptors, but refers to the value of the largest file descriptor +1 contained
readfds: readable file descriptor set
writefds: writable file descriptor set
exceptfds: abnormal file descriptor set
timeout timeout Set up, block. If the timeout setting is not implemented, the function will die and wait

*/

   int select(int nfds, fd_set *readfds, fd_set *writefds,
              fd_set *exceptfds, struct timeval *timeout);

   void FD_CLR(int fd, fd_set *set);//从指定的文件描述符集合中删除指定文件描述符fd
   int  FD_ISSET(int fd, fd_set *set);//判断文件描述符fd 是否存在于文件描述符集合set中
   void FD_SET(int fd, fd_set *set);// 将文件描述符fd 放到 文件描述符集合set中
   void FD_ZERO(fd_set *set); //清空一个文件描述符集合

The
return value of RETURN VALUE is the number of file descriptor behaviors in which events of interest have occurred, and these file descriptors are still placed in the read set, write set, and error set in which file descriptor set.
If it fails, -1 is returned, and errno is set. EINTR is a false error, blocking waiting can be interrupted by a signal

ERRORS

   EINTR  A signal was caught; see signal(7). 阻塞等待 可以被信号打断

   EINVAL nfds is negative or exceeds the RLIMIT_NOFILE resource limit (see getrlimit(2)).

   EINVAL the value contained within timeout is invalid.

   ENOMEM unable to allocate memory for internal tables.

   The time structures involved are defined in <sys/time.h> and look like

秒+微秒
           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

Note that select (NULL, NULL, NULL, NULL, set timeout) is often used to achieve a safe sleep.

Experiment 1: Improve the state machine to use multiplexed IO to copy data between two devices

The program is no longer in the loop of false error rereading and rewriting all the time, and the cpu usage rate is greatly reduced, because most of the time is blocked in select() waiting for file descriptor changes.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>

#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFSIZE 1024

enum
{
	STATE_R = 1,
	STATE_W,
STATE_AUTO,
	STATE_Ex,
	STATE_T
};

struct fsm_st
{
	int state;
	int sfd;
	int dfd;
	char buf[BUFSIZE];
	int len;
	int pos;
	char* errstr;
};

static void fsm_driver(struct fsm_st *fsm)
{
	int ret;

	switch(fsm->state)
	{
		case STATE_R:
			fsm->len = read(fsm->sfd,fsm->buf,BUFSIZE);
			if(fsm->len == 0)
				fsm->state = STATE_T;
			else if(fsm->len < 0)
			{
				if(errno == EAGAIN)
					fsm->state = STATE_R;

				else
				{
					fsm->errstr = "read()";
					fsm->state = STATE_Ex;
				}	
					
			}
			else
			{		
				fsm->pos = 0;
				fsm->state = STATE_W;
			}
		
		break;

		case STATE_W:
			ret = write(fsm->dfd,fsm->buf+fsm->pos,fsm->len);
			if(ret < 0)
			{
				if(errno == EAGAIN)
					fsm->state = STATE_W;
				else
				{
					fsm->errstr = "read()";
					fsm->state = STATE_Ex;
				}
				
			}
			else
			{
				fsm->pos += ret;
				fsm->len -= ret;
				if(fsm->len == 0)
					fsm->state = STATE_R;
				else 
					fsm->state = STATE_W;
			}
		break;

		case STATE_Ex:
			perror(fsm->errstr);
			fsm->state = STATE_T;		
		break;

		case STATE_T:
			/* do something*/
		break;
		
		default:
			abort();
		break;
	}

}

static int max(int a,int b)
{
	if(a > b)
	{	
		return a;
	}
	return b;
}

static void relay(int fd1,int fd2)
{
	int fd1_save,fd2_save;
	struct fsm_st fsm12,fsm21;
	fd_set rset,wset;	


	fd1_save = fcntl(fd1,F_GETFL);
	fcntl(fd1,F_SETFL,fd1_save|O_NONBLOCK);

	fd2_save = fcntl(fd2,F_GETFL);
	fcntl(fd2,F_SETFL,fd2_save|O_NONBLOCK);

	fsm12.state = STATE_R;
	fsm12.sfd = fd1;
	fsm12.dfd = fd2;

	fsm21.state = STATE_W;
	fsm21.sfd = fd2;
	fsm21.dfd = fd1;	

	while(fsm12.state != STATE_T || fsm21.state != STATE_T)	
	{
	//设置现场
		FD_ZERO(&rset);
		FD_ZERO(&wset);

		if(fsm12.state == STATE_R)
			FD_SET(fsm12.sfd,&rset);

		if(fsm12.state == STATE_W)
			FD_SET(fsm12.dfd,&wset);

		if(fsm21.state == STATE_R)
			FD_SET(fsm21.sfd,&rset);

		if(fsm21.state == STATE_W)
			FD_SET(fsm21.dfd,&wset);

  	//监控
		if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO)
		{
			if(select(max(fd1,fd2)+1,&rset,&wset,NULL,NULL) < 0)	
			{
				if(errno == EINTR)
					continue;

				perror("select()");
				exit(1);
			}
		}
	
 	//根据监控结果 最下一步动作
		if(FD_ISSET(fd1,&rset) || FD_ISSET(fd2,&wset) || fsm12.state > STATE_AUTO)
		fsm_driver(&fsm12);

		if(FD_ISSET(fd2,&rset) || FD_ISSET(fd1,&wset) || fsm12.state > STATE_AUTO)
		fsm_driver(&fsm21);
	}

	fcntl(fd1,F_SETFL,fd1_save);
	fcntl(fd2,F_SETFL,fd2_save);

}

int main()
{

	int fd1,fd2;

	fd1 = open(TTY1,O_RDWR);
	if(fd1 < 0)
	{

		perror("open()");
		exit(1);
	}

	fd2 = open(TTY2,O_RDWR|O_NONBLOCK);
	if(fd1 < 0)
	{
		perror("open()");
		exit(1);
	}

	relay(fd1,fd2);


	close(fd2);
	close(fd1);
	

}

Problems with select():

1 The monitoring site location (three sets) is the same as the monitoring result location (three sets), and the same space is used. If the read set, write set, and error set each set 10 file descriptors, a total of 30 file descriptions As long as any file descriptor changes, such as a file descriptor in the read set becomes readable, the function returns, that is, the number of file descriptors that can be read in the read set, which is 1. The other collections will be emptied. To monitor again, you need to reset the monitoring site.

2 The number of file descriptors that can be opened in a process can be changed, ulimit -a. Then there is a problem with the first parameter nfds. If the number of file descriptors we monitor exceeds the maximum signed integer range, it will overflow.

select() organizes file descriptors in units of events

Guess you like

Origin blog.csdn.net/LinuxArmbiggod/article/details/114670289