Linux系統編程---I/O多路復用

文章目錄

  • 1 什么是IO多路復用
  • 2 解決什么問題
    • 說在前面
    • I/O模型
      • 阻塞I/O
      • 非阻塞I/O
      • IO多路復用
      • 信號驅動IO
      • 異步IO
  • 3 目前有哪些IO多路復用的方案
    • 解決方案總覽
    • 常見軟件的IO多路復用方案
  • 4 具體怎么用
    • select
    • poll
    • epoll
      • level-triggered and edge-triggered
      • 狀態變化通知(edge-triggered)模式下的epoll
  • 5 不同IO多路復用方案優缺點
    • poll vs select
    • epoll vs poll&select

1 什么是IO多路復用

一句話解釋:單線程或單進程同時監測若干個文件描述符是否可以執行I/O操作

2 解決什么問題

說在前面

應用程序通常需要處理來自多條事件流中的事件,比如我現在用的電腦,需要同時處理鍵盤鼠標的輸入、中斷信號等等事件,再比如web服務器如nginx,需要同時處理來來自N個客戶端的事件。

邏輯控制流在時間上的重疊叫做 并發

而CPU單核在同一時刻只能做一件事情,一種解決辦法是對CPU進行時分復用(多個事件流將CPU切割成多個時間片,不同事件流的時間片交替進行)。在計算機系統中,我們用線程或者進程來表示一條執行流,通過不同的線程或進程在操作系統內部的調度,來做到對CPU處理的時分復用。這樣多個事件流就可以并發進行,不需要一個等待另一個太久,在用戶看起來他們似乎就是并行在做一樣。

但凡事都是有成本的。線程/進程也一樣,有這么幾個方面:

  1. 線程/進程創建成本
  2. CPU切換不同線程/進程成本 Context Switch
  3. 多線程的資源競爭

有沒有一種可以在單線程/進程中處理多個事件流的方法呢?一種答案就是IO多路復用。

因此IO多路復用解決的本質問題是在用更少的資源完成更多的事。

為了更全面的理解,先介紹下在Linux系統下所有IO模型。

I/O模型

目前Linux系統中提供了5中IO處理模型

  1. 阻塞IO
  2. 非阻塞IO
  3. IO多路復用
  4. 信號驅動IO
  5. 異步IO

阻塞I/O

這是最常用的簡單的IO模型。阻塞IO意味著當我們發起一次IO操作后一直等待成功或失敗之后才返回,在這期間程序不能做其它的事情。阻塞IO操作只能對單個文件描述符進行操作,詳見read或write。

非阻塞I/O

我們在發起IO時,通過對文件描述符設置O_NONBLOCK flag來指定該文件描述符的IO操作為非阻塞。非阻塞IO通常發生在一個for循環當中,因為每次進行IO操作時要么IO操作成功,要么當IO操作會阻塞時返回錯誤EWOULDBLOCK/EAGAIN,然后再根據需要進行下一次的for循環操作,這種類似輪詢的方式會浪費很多不必要的CPU資源,是一種糟糕的設計。和阻塞IO一樣,非阻塞IO也是通過調用read或write來進行操作的,也只能對單個描述符進行操作。

IO多路復用

IO多路復用在Linux下包括了三種,select、poll、epoll,抽象來看,他們功能是類似的,但具體細節各有不同:首先都會對一組文件描述符進行相關事件的注冊,然后阻塞等待某些事件的發生或等待超時。更多細節詳見下面的 “具體怎么用”。IO多路復用都可以關注多個文件描述符,但對于這三種機制而言,不同數量級文件描述符對性能的影響是不同的,下面會詳細介紹。

信號驅動IO

信號驅動IO是利用信號機制,讓內核告知應用程序文件描述符的相關事件。這里有一個信號驅動IO相關的例子。

但信號驅動IO在網絡編程的時候通常很少用到,因為在網絡環境中,和socket相關的讀寫事件太多了,比如下面的事件都會導致SIGIO信號的產生:

  1. TCP連接建立
  2. 一方斷開TCP連接請求
  3. 斷開TCP連接請求完成
  4. TCP連接半關閉
  5. 數據到達TCP socket
  6. 數據已經發送出去(如:寫buffer有空余空間)

上面所有的這些都會產生SIGIO信號,但我們沒辦法在SIGIO對應的信號處理函數中區分上述不同的事件,SIGIO只應該在IO事件單一情況下使用,比如說用來監聽端口的socket,因為只有客戶端發起新連接的時候才會產生SIGIO信號。

異步IO

異步IO和信號驅動IO差不多,但它比信號驅動IO可以多做一步:相比信號驅動IO需要在程序中完成數據從用戶態到內核態(或反方向)的拷貝,異步IO可以把拷貝這一步也幫我們完成之后才通知應用程序。我們使用 aio_read 來讀,aio_write 寫。

同步IO vs 異步IO 1. 同步IO指的是程序會一直阻塞到IO操作如read、write完成 2.異步IO指的是IO操作不會阻塞當前程序的繼續執行
所以根據這個定義,上面阻塞IO當然算是同步的IO,非阻塞IO也是同步IO,因為當文件操作符可用時我們還是需要阻塞的讀或寫,同理IO多路復用和信號驅動IO也是同步IO,只有異步IO是完全完成了數據的拷貝之后才通知程序進行處理,沒有阻塞的數據讀寫過程。

3 目前有哪些IO多路復用的方案

解決方案總覽

Linux: select、poll、epoll

MacOS/FreeBSD: kqueue

Windows/Solaris: IOCP

常見軟件的IO多路復用方案

redis: Linux下 epoll(level-triggered),沒有epoll用select

nginx: Linux下 epoll(edge-triggered),沒有epoll用select

4 具體怎么用

select

相關函數定義如下:

       /* 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>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);int  FD_ISSET(int fd, fd_set *set);void FD_SET(int fd, fd_set *set);void FD_ZERO(fd_set *set);#include <sys/select.h>int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);

select的調用會阻塞到有文件描述符可以進行IO操作或被信號打斷或者超時才會返回。

select將監聽的文件描述符分為三組,每一組監聽不同的需要進行的IO操作,readfds是需要進行讀操作的文件描述符,writefds是需要進行寫操作的文件描述符,exceptfds是需要進行異常事件處理的文件描述符。這三個參數可以用NULL來表示對應的事件不需要監聽。

nfds: 要監視的文件描述符的范圍,一般取監視的描述符數的最大值+1,如這里寫 10, 這樣的話,描述符 0,1, 2 …… 9 都會被監視

當select返回時,每組文件描述符會被select過濾只留下可以進行對應IO操作的文件描述符,也就是說能進行IO操作的文件描述符會被設置。成功:就緒描述符的數目,超時返回 0,出錯:-1 。

FD_xx系統函數時用來操作文件描述符組合文件描述符的關系。

FD_ZERO用來清空文件描述符組。每次調用select前都需要清空一次。

fd_set writefds;
FD_ZERO(&writefds)

FD_SET添加一個文件描述符到組中,FD_CLR對應將一個文件描述符移出組中

FD_SET(fd, &writefds);
FD_CLR(fd, &writefds);

FD_ISSET檢測一個文件描述符是否在組中,我們用這個來檢測一次select調用之后有哪些文件描述符可以進行IO操作

if (FD_ISSET(fd, &readfds)){/* fd可讀 */
}

select可同時監聽的文件描述符數量是通過FS_SETSIZE來限制的,在Linux系統中,該值為1024,當然我們可以增大這個值,但隨著監聽的文件描述符數量增加,select的效率會降低,我們會在『不同IO多路復用方案優缺點』一節中展開。

打開鏈接查看完整的使用select的例子

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>#define TIMEOUT 5 /* select timeout in seconds */
#define BUF_LEN 1024 /* read buffer in bytes */int main (void) {struct timeval tv;fd_set readfds;int ret;/* Wait on stdin for input. */FD_ZERO(&readfds);FD_SET(STDIN_FILENO, &readfds);/* Wait up to five seconds. */tv.tv_sec = TIMEOUT;tv.tv_usec = 0;/* All right, now block! */ret = select (STDIN_FILENO + 1, &readfds,NULL,NULL, &tv);if (ret == ?1) {perror ("select");return 1; } else if (!ret) {printf ("%d seconds elapsed.\n", TIMEOUT);return 0; }/** Is our file descriptor ready to read?* (It must be, as it was the only fd that* we provided and the call returned* nonzero, but we will humor ourselves.)*/if (FD_ISSET(STDIN_FILENO, &readfds)) {char buf[BUF_LEN+1];int len;/* guaranteed to not block */len = read (STDIN_FILENO, buf, BUF_LEN);if (len == ?1) {perror ("read");return 1; }if (len) {buf[len] = '\0';printf ("read: %s\n", buf);}return 0; }fprintf (stderr, "This should not happen!\n");return 1; 
}

在這里插入圖片描述

poll

相關函數定義

       #include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);#define _GNU_SOURCE         /* See feature_test_macros(7) */#include <signal.h>#include <poll.h>int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask);struct pollfd {int   fd;         /* file descriptor */short events;     /* requested events */short revents;    /* returned events */};

poll函數監視在fds數組指明的一組文件描述符上發生的動作,當滿足條件或者超時的時候會退出

  • 參數fds是一個指向pollfd數組的指針,監視的文件描述符和條件放在里面
  • 參數nfds是比監視的最大描述符的值大于1的值
  • 參數timeout是超時時間,單位為毫秒,當為負值時,表示永遠等待

結構體struct pollfd的成員含義如下:

  • 成員fd表示監視的文件描述符
  • 成員events表示輸入的監視事件
  • 成員revents表示返回的監視事件,即返回時發生的事件

和select用三組文件描述符不同的是,poll只有一個pollfd數組,數組中的每個元素都表示一個需要監聽IO操作事件的文件描述符。events參數是我們等待的事件,revents是實際發生了的事件。

打開鏈接查看完整的使用poll的例子

#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#define TIMEOUT 5 /* poll timeout, in seconds */
int main (void) {struct pollfd fds[2];int ret;/* watch stdin for input */fds[0].fd = STDIN_FILENO;fds[0].events = POLLIN;/* watch stdout for ability to write (almost always true) */fds[1].fd = STDOUT_FILENO;fds[1].events = POLLOUT;/* All set, block! */ret = poll (fds, 2, TIMEOUT * 1000);if (ret == ?1) {perror ("poll");return 1; }if (!ret) {printf ("%d seconds elapsed.\n", TIMEOUT);return 0; }if (fds[0].revents & POLLIN)printf ("stdin is readable\n");if (fds[1].revents & POLLOUT)printf ("stdout is writable\n");return 0; 
}/*$ ./pollstdout is writable$ ./poll < some_filestdin is readablestdout is writable
*/

epoll

相關函數定義如下

    #include <sys/epoll.h>int epoll_create(int size);int epoll_create1(int flags);int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);int epoll_pwait(int epfd, struct epoll_event *events,int maxevents, int timeout,const sigset_t *sigmask);
// 保存觸發事件的某個文件描述符相關的數據(與具體使用方式有關)                
typedef union epoll_data {void        *ptr;int          fd;uint32_t     u32;uint64_t     u64;} epoll_data_t;
// 感興趣的事件和被觸發的事件struct epoll_event {uint32_t     events;      /* Epoll events */epoll_data_t data;        /* User data variable */};

epoll_create&epoll_create1用于創建一個epoll實例,而epoll_ctl用于往epoll實例中增刪改要監測的文件描述符,epoll_wait則用于阻塞的等待可以執行IO操作的文件描述符直到超時。

epoll_create:自從 linux 2.6.8 之后,size 參數是被忽略的,也就是說可以填只有大于 0 的任意值。

epoll_ctl:epfd: epoll 專用的文件描述符,epoll_create()的返回值;op: 表示動作,用三個宏來表示:

EPOLL_CTL_ADD:注冊新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從 epfd 中刪除一個 fd;

fd: 需要監聽的文件描述符;

結構體epoll_event 成員含義如下:

  • 成員 events 代表要監聽的 epoll 事件類型,有讀事件,寫事件,有如下取值。
    在這里插入圖片描述
  • data:用戶數據變量

epoll_wait等待事件的產生,類似于select()調用。參數events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個 maxevents的值不能大于創建epoll_create()時的size,參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。如果返回–1,則表示出現錯誤,需要檢查 errno錯誤碼判斷錯誤類型。

  • 第1個參數 epfd是 epoll的描述符。

  • 第2個參數 events則是分配好的 epoll_event結構體數組,epoll將會把發生的事件復制到 events數組中(events不可以是空指針,內核只負責把數據復制到這個 events數組中,不會去幫助我們在用戶態中分配內存。內核這種做法效率很高)。

  • 第3個參數 maxevents表示本次可以返回的最大事件數目,通常 maxevents參數與預分配的events數組的大小是相等的。

  • 第4個參數 timeout表示在沒有檢測到事件發生時最多等待的時間(單位為毫秒),如果 timeout為0,則表示 epoll_wait在 rdllist鏈表中為空,立刻返回,不會等待。

打開鏈接查看完整的使用epoll的例子

//https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>#define MAXEVENTS 64static int
make_socket_non_blocking (int sfd)
{int flags, s;flags = fcntl (sfd, F_GETFL, 0);if (flags == -1){perror ("fcntl");return -1;}flags |= O_NONBLOCK;s = fcntl (sfd, F_SETFL, flags);if (s == -1){perror ("fcntl");return -1;}return 0;
}static int
create_and_bind (char *port)
{struct addrinfo hints;struct addrinfo *result, *rp;int s, sfd;memset (&hints, 0, sizeof (struct addrinfo));hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */hints.ai_flags = AI_PASSIVE;     /* All interfaces */s = getaddrinfo (NULL, port, &hints, &result);if (s != 0){fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));return -1;}for (rp = result; rp != NULL; rp = rp->ai_next){sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);if (sfd == -1)continue;s = bind (sfd, rp->ai_addr, rp->ai_addrlen);if (s == 0){/* We managed to bind successfully! */break;}close (sfd);}if (rp == NULL){fprintf (stderr, "Could not bind\n");return -1;}freeaddrinfo (result);return sfd;
}int
main (int argc, char *argv[])
{int sfd, s;int efd;struct epoll_event event;struct epoll_event *events;if (argc != 2){fprintf (stderr, "Usage: %s [port]\n", argv[0]);exit (EXIT_FAILURE);}sfd = create_and_bind (argv[1]);if (sfd == -1)abort ();s = make_socket_non_blocking (sfd);if (s == -1)abort ();s = listen (sfd, SOMAXCONN);if (s == -1){perror ("listen");abort ();}efd = epoll_create1 (0);if (efd == -1){perror ("epoll_create");abort ();}event.data.fd = sfd;event.events = EPOLLIN | EPOLLET;s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);if (s == -1){perror ("epoll_ctl");abort ();}/* Buffer where events are returned */events = calloc (MAXEVENTS, sizeof event);/* The event loop */while (1){int n, i;n = epoll_wait (efd, events, MAXEVENTS, -1);for (i = 0; i < n; i++){if ((events[i].events & EPOLLERR) ||(events[i].events & EPOLLHUP) ||(!(events[i].events & EPOLLIN))){/* An error has occured on this fd, or the socket is notready for reading (why were we notified then?) */fprintf (stderr, "epoll error\n");close (events[i].data.fd);continue;}else if (sfd == events[i].data.fd){/* We have a notification on the listening socket, whichmeans one or more incoming connections. */while (1){struct sockaddr in_addr;socklen_t in_len;int infd;char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];in_len = sizeof in_addr;infd = accept (sfd, &in_addr, &in_len);if (infd == -1){if ((errno == EAGAIN) ||(errno == EWOULDBLOCK)){/* We have processed all incomingconnections. */break;}else{perror ("accept");break;}}s = getnameinfo (&in_addr, in_len,hbuf, sizeof hbuf,sbuf, sizeof sbuf,NI_NUMERICHOST | NI_NUMERICSERV);if (s == 0){printf("Accepted connection on descriptor %d ""(host=%s, port=%s)\n", infd, hbuf, sbuf);}/* Make the incoming socket non-blocking and add it to thelist of fds to monitor. */s = make_socket_non_blocking (infd);if (s == -1)abort ();event.data.fd = infd;event.events = EPOLLIN | EPOLLET;s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);if (s == -1){perror ("epoll_ctl");abort ();}}continue;}else{/* We have data on the fd waiting to be read. Read anddisplay it. We must read whatever data is availablecompletely, as we are running in edge-triggered modeand won't get a notification again for the samedata. */int done = 0;while (1){ssize_t count;char buf[512];count = read (events[i].data.fd, buf, sizeof buf);if (count == -1){/* If errno == EAGAIN, that means we have read alldata. So go back to the main loop. */if (errno != EAGAIN){perror ("read");done = 1;}break;}else if (count == 0){/* End of file. The remote has closed theconnection. */done = 1;break;}/* Write the buffer to standard output */s = write (1, buf, count);if (s == -1){perror ("write");abort ();}}if (done){printf ("Closed connection on descriptor %d\n",events[i].data.fd);/* Closing the descriptor will make epoll remove itfrom the set of descriptors which are monitored. */close (events[i].data.fd);}}}}free (events);close (sfd);return EXIT_SUCCESS;
}

level-triggered and edge-triggered

這兩種底層的事件通知機制通常被稱為水平觸發和邊沿觸發,真是翻譯的詞不達意,如果我來翻譯,我會翻譯成:狀態持續通知和狀態變化通知。

這兩個概念來自電路,triggered代表電路激活,也就是有事件通知給程序,level-triggered表示只要有IO操作可以進行比如某個文件描述符有數據可讀,每次調用epoll_wait都會返回以通知程序可以進行IO操作,edge-triggered表示只有在文件描述符狀態發生變化時,調用epoll_wait才會返回,如果第一次沒有全部讀完該文件描述符的數據而且沒有新數據寫入,再次調用epoll_wait都不會有通知給到程序,因為文件描述符的狀態沒有變化。

select和poll都是狀態持續通知的機制,且不可改變,只要文件描述符中有IO操作可以進行,那么select和poll都會返回以通知程序。而epoll兩種通知機制可選。

狀態變化通知(edge-triggered)模式下的epoll

在epoll狀態變化通知機制下,有一些的特殊的地方需要注意。考慮下面這個例子

  1. 服務端文件描述符rfd代表要執行read操作的TCP socket,rfd已被注冊到一個epoll實例中
  2. 客戶端向rfd寫了2kb數據
  3. 服務端調用epoll_wait返回,rfd可執行read操作
  4. 服務端從rfd中讀取了1kb數據
  5. 服務端又調用了一次epoll_wait

在第5步的epoll_wait調用不會返回,而對應的客戶端會因為服務端沒有返回對應的response而超時重試,原因就是我上面所說的,epoll_wait只會在狀態變化時才會通知程序進行處理。第3步epoll_wait會返回,是因為客戶端寫了數據,導致rfd狀態被改變了,第3步的epoll_wait已經消費了這個事件,所以第5步的epoll_wait不會返回。

我們需要配合非阻塞IO來解決上面的問題:

  1. 對需要監聽的文件描述符加上非阻塞IO標識
  2. 只在read或者write返回EAGAIN或EWOULDBLOCK錯誤時,才調用epoll_wait等待下次狀態改變發生

通過上述方式,我們可以確保每次epoll_wait返回之后,我們的文件描述符中沒有讀到一半或寫到一半的數據。

5 不同IO多路復用方案優缺點

poll vs select

poll和select基本上是一樣的,poll相比select好在如下幾點:

  1. poll傳參對用戶更友好。比如不需要和select一樣計算很多奇怪的參數比如nfds(值最大的文件描述符+1),再比如不需要分開三組傳入參數。
  2. poll會比select性能稍好些,因為select是每個bit位都檢測,假設有個值為1000的文件描述符,select會從第一位開始檢測一直到第1000個bit位。但poll檢測的是一個數組。
  3. select的時間參數在返回的時候各個系統的處理方式不統一,如果希望程序可移植性更好,需要每次調用select都初始化時間參數。

而select比poll好在下面幾點

  1. 支持select的系統更多,兼容更強大,有一些unix系統不支持poll
  2. select提供精度更高(到microsecond)的超時時間,而poll只提供到毫秒的精度。

但總體而言 select和poll基本一致。

epoll vs poll&select

epoll優于select&poll在下面幾點:

  1. 在需要同時監聽的文件描述符數量增加時,select&poll是O(N)的復雜度,epoll是O(1),在N很小的情況下,差距不會特別大,但如果N很大的前提下,一次O(N)的循環可要比O(1)慢很多,所以高性能的網絡服務器都會選擇epoll進行IO多路復用。
  2. epoll內部用一個文件描述符掛載需要監聽的文件描述符,這個epoll的文件描述符可以在多個線程/進程共享,所以epoll的使用場景要比select&poll要多。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/379551.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379551.shtml
英文地址,請注明出處:http://en.pswp.cn/news/379551.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[轉帖]純屬娛樂——變形金剛vs天網

[轉帖]變形金剛2的影評-《變形金剛3 天網反擊戰》有一個問題困擾了我足足二十年&#xff1a;為什么汽車人要幫地球人&#xff1f;光用“所有有感知的生物都應享有自由”這個法則是根本說不過去的&#xff0c;因為豬也有感知&#xff0c;但人類就把豬圈養起來&#xff0c;隨意殺…

c#中textbox屬性_C#.Net中的TextBox.MaxLength屬性與示例

c#中textbox屬性Here we are demonstrating use of MaxLength property of TextBox. 在這里&#xff0c;我們演示了TextBox的MaxLength屬性的使用。 MaxLength property of TextBox is used to set maximum number of character that we can input into a TextBox. Limit of M…

IIS7 MVC網站生成、發布

(1)生成。 確保System.Web.Mvc.dll在bin目錄下 (2)發布網站到文件系統 (3)在IIS中為網站添加應用程序池&#xff08;一個虛擬目錄&#xff0c;一個應用程序池&#xff09; (4)添加在默認網站下添加虛擬目錄 &#xff08;5&#xff09;轉換為應用程序 至此&#xff0c;部署完畢 …

標題:明碼

轉載&#xff1a;https://blog.csdn.net/u011747888/article/details/79781040 標題&#xff1a;明碼 漢字的字形存在于字庫中&#xff0c;即便在今天&#xff0c;16點陣的字庫也仍然使用廣泛。 16點陣的字庫把每個漢字看成是16x16個像素信息。并把這些信息記錄在字節中。 一…

C語言多維數組

文章目錄多維數組數組名下標指向數組的指針作為函數參數的多維數組指針數組小結多維數組 如果某個數組的維數超過1&#xff0c;它就被稱為多維數組&#xff0c;例如&#xff0c;下面這個聲明&#xff1a; int matrix[6][10]創建了一個包含60個元素的矩陣。但是&#xff0c;它…

ubuntu路由器聯網_路由器及其協議簡介| 聯網

ubuntu路由器聯網路由器簡介 (Introduction to Router) Routers are network layer devices. Data on the network layer is known as packets. Routers work to forward packets from one network to another. Routers also maintain the address table. 路由器是網絡層設備。…

XPath學習:軸(5)——descendant-or-self

XPath 是一門在 XML 文檔中查找信息的語言。XPath 可用來在 XML 文檔中對元素和屬性進行遍歷。 XPath 是 W3C XSLT 標準的主要元素&#xff0c;并且 XQuery 和 XPointer 同時被構建于 XPath 表達之上。 推薦一個挺不錯的網站&#xff1a;http://www.zvon.org/xxl/XPathTutorial…

linux設備驅動開發---平臺設備驅動程序

文章目錄1 平臺驅動程序2 平臺設備2.1 資源和平臺數據1 設備配置---廢棄的舊方法資源平臺數據聲明平臺設備2 設備配置---推薦的新方法3 設備、驅動程序和總線匹配OF風格ACPIID表匹配匹配平臺設備的名字和平臺驅動的名字平臺設備和平臺驅動程序如何匹配4 Platfrom架構驅動程序有…

標題:乘積尾零

標題&#xff1a;乘積尾零 如下的10行數據&#xff0c;每行有10個整數&#xff0c;請你求出它們的乘積的末尾有多少個零&#xff1f; 5650 4542 3554 473 946 4114 3871 9073 90 4329 2758 7949 6113 5659 5245 7432 3051 4434 6704 3594 9937 1173 6866 3397 4759 7557 3070…

Robots.txt指南

Robots.txt指南當搜索引擎訪問一個網站時&#xff0c;它首先會檢查該網站的根域下是否有一個叫做robots.txt的純文本文件。Robots.txt文件用于限定搜索引擎對其 網站的訪問范圍&#xff0c;即告訴搜索引擎網站中哪些文件是允許它進行檢索(下載)的。這就是大家在網絡上常看到的“…

fwrite函數的用法示例_C語言中的fwrite()函數(帶有示例)

fwrite函數的用法示例C中的fwrite()函數 (fwrite() function in C) Prototype: 原型&#xff1a; size_t fwrite(void *buffer, size_t length, size_t count, FILE *filename);Parameters: 參數&#xff1a; void *buffer, size_t length, size_t count, FILE *filenameRetu…

標題:遞增三元組

標題&#xff1a;遞增三元組 給定三個整數數組 A [A1, A2, … AN], B [B1, B2, … BN], C [C1, C2, … CN]&#xff0c; 請你統計有多少個三元組(i, j, k) 滿足&#xff1a; 1 < i, j, k < NAi < Bj < Ck 【輸入格式】 第一行包含一個整數N。 第二行包含N個整…

伙伴算法、slab機制、內存管理函數

文章目錄1 伙伴算法頁框操作alloc_pages()2 slabslab機制要解決的問題使用高速緩存3 內存管理函數kmallockzallocvmallocvzalloc區別參考文章內核使用struct page結構體描述每個物理頁&#xff0c;也叫頁框。內核在很多情況下&#xff0c;需要申請連續的頁框&#xff0c;而且數…

eval 函數 代替函數_eval()函數以及JavaScript中的示例

eval 函數 代替函數eval()函數 (eval() function) eval() function is a predefined global function in JavaScript and it is used to evaluate (execute) an expression, which is passed to the function as a parameter. It can also evaluate any JavaScript code. eval(…

F# ≥ C#(活動模式 和枚舉)

F#提供了一個叫"活動模式"的有趣功能。它把輸入的數據轉換成其他不同的東西。 一個有趣的使用實例就是代替枚舉。但我編程枚舉的時候&#xff0c;我總不高興去鏈接枚舉項到它的定義。例如&#xff0c;下面的枚舉定義了 數字枚舉&#xff0c; enum Numbers{Odd,Even,}…

關于java的classpath設置

今天晚上實驗室的另一個人在編譯一個java程序&#xff0c;需要用到一個jar文件&#xff0c;所以在命令行編譯的時候需要添加jar的路徑&#xff0c;例如&#xff1a; java -classpath demo.jar hello 但是設置了path之后java就不會搜索當前目錄&#xff0c;也就是所如果hello在當…

C語言uthash的用法

文章目錄1 定義一個哈希表鍵值UT_hash_handle2 哈希操作聲明添加查找刪除獲取哈希表中元素個數迭代排序3 案例鍵的使用官網解釋&#xff1a;https://troydhanson.github.io/uthash/userguide.html 在使用之前&#xff0c;我們必須包含uthash.h的頭文件&#xff0c;你需要將該頭…

Javascript Paste Keyboard Shortcuts Hijack

author : kj021320 team : I.S.T.O 這樣的攻擊手段也算是極其無恥 猥瑣之極! 所以防御措施一定要做好 首先說一下通過Javascript Paste Keyboard Shortcuts Hijack能做什么???能夠讀取你本地機器任何文件! 沒錯!也就是說 你中了任何一個XSS 加上你按了粘貼快捷鍵后,你就有可…

python 生成器表達式_Python中的列表理解與生成器表達式

python 生成器表達式The list is a collection of different types of elements and there are many ways of creating a list in Python. 該列表是不同類型元素的集合&#xff0c;并且有許多方法可以在Python中創建列表。 清單理解 (List Comprehension) List comprehension…

Javaweb---監聽器

1.什么是監聽器 監聽器就是監聽某個對象的狀態變化的組件。 事件源&#xff1a;被監聽的對象 ----- 三個域對象 request session servletContext 監聽器&#xff1a;監聽事件源對象 事件源對象的狀態的變化都會觸發監聽器 ---- 62 注冊監聽器&#xff1a;將監聽器與事件源進行…