目录
文件IO的其他常用函数总结
1、设备驱动程序的通用接口
函数原型:
参数说明:
返回值:
常见用途:
示例代码1:
示例代码2:
示例代码3:
常见的请求代码:
注意事项:
2、文件描述符进行各种控制操作
函数原型:
参数说明:
常见命令:
返回值:
示例代码 1:
设置文件描述符为非阻塞模式
示例代码 2:
复制文件描述符
示例代码 3:
设置文件描述符标志(FD_CLOEXEC)
常见用途:
3、多路复用 I/O ,让进程监视多个文件描述符1
函数原型:
参数说明:
返回值:
示例代码:
4、多路复用 I/O ,监视多个文件描述符2
函数原型:
参数说明:
返回值:
示例代码:
使用 poll 函数监视标准输入和一个套接字文件描述符是否可读
5、高效的 I/O 多路复用机制
主要特点:
主要函数:
示例代码:
监视标准输入和一个套接字文件描述符是否可读:
文件IO的其他常用函数总结
1、设备驱动程序的通用接口
在 Linux 系统中,ioctl
(Input/Output Control)函数是一种用于设备驱动程序的通用接口,允许用户空间程序对设备进行低级控制。它通常用于执行那些无法通过标准 I/O 操作(如 read
和 write
)完成的特殊操作。
函数原型:
int ioctl(int fd, unsigned long request, ...);
参数说明:
-
fd
:文件描述符,表示要操作的设备或文件。通常是通过open
函数打开设备文件后获得的。 -
request
:请求代码,用于指定要执行的具体操作。请求代码通常由设备驱动程序定义,并在头文件中声明。 -
...
:可选参数,具体取决于请求代码。它可以是一个指针、一个整数或其他类型的数据,用于传递或接收与请求相关的数据。
返回值:
-
成功时返回 0。
-
失败时返回 -1,并设置
errno
以指示错误原因。
常见用途:
ioctl
函数的用途非常广泛,以下是一些常见的应用场景:
-
设备配置:设置或获取设备的参数,例如波特率、数据位、停止位等。
-
特殊操作:执行设备特有的操作,如清空缓冲区、复位设备等。
-
硬件控制:直接控制硬件设备,例如设置 GPIO 引脚状态、读取硬件寄存器等。
-
网络设备:配置网络接口参数,如设置 IP 地址、启用/禁用接口等。
示例代码1:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <termios.h>
int main() {int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);if (fd == -1) {perror("open");return -1;}
struct termios options;if (tcgetattr(fd, &options) != 0) {perror("tcgetattr");close(fd);return -1;}
cfsetispeed(&options, B9600); // 设置输入波特率为 9600cfsetospeed(&options, B9600); // 设置输出波特率为 9600
if (tcsetattr(fd, TCSANOW, &options) != 0) {perror("tcsetattr");close(fd);return -1;}
printf("Serial port baud rate set to 9600\n");close(fd);return 0;
}
示例代码2:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define DEVICE_FILE "/dev/my_device" // 替换为你的字符设备文件路径
#define SET_DEVICE_PARAM _IOW('k', 1, int)
#define GET_DEVICE_PARAM _IOR('k', 2, int)
int main() {int fd;int param;
// 打开字符设备文件fd = open(DEVICE_FILE, O_RDWR);if (fd == -1) {perror("open");return -1;}
// 设置设备参数param = 42; // 假设我们想设置的参数值为 42if (ioctl(fd, SET_DEVICE_PARAM, ¶m) == -1) {perror("ioctl SET_DEVICE_PARAM");close(fd);return -1;}printf("Device parameter set to %d\n", param);
// 获取设备参数if (ioctl(fd, GET_DEVICE_PARAM, ¶m) == -1) {perror("ioctl GET_DEVICE_PARAM");close(fd);return -1;}printf("Device parameter retrieved: %d\n", param);
// 关闭设备文件close(fd);return 0;
}
示例代码3:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#define INTERFACE "eth0" // 替换为你的网络接口名称,例如 "eth0" 或 "wlan0"
int main() {int sockfd;struct ifreq ifr;
// 创建一个套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket");return -1;}
// 初始化 ifreq 结构memset(&ifr, 0, sizeof(ifr));strncpy(ifr.ifr_name, INTERFACE, IFNAMSIZ);
// 获取当前 IP 地址if (ioctl(sockfd, SIOCGIFADDR, &ifr) < 0) {perror("ioctl SIOCGIFADDR");close(sockfd);return -1;}
// 打印当前 IP 地址struct sockaddr_in *addr = (struct sockaddr_in *)&ifr.ifr_addr;printf("Current IP address of %s: %s\n", INTERFACE, inet_ntoa(addr->sin_addr));
// 设置新的 IP 地址struct sockaddr_in new_addr;memset(&new_addr, 0, sizeof(new_addr));new_addr.sin_family = AF_INET;new_addr.sin_addr.s_addr = inet_addr("192.168.1.100"); // 替换为你想要设置的 IP 地址
memcpy(&ifr.ifr_addr, &new_addr, sizeof(new_addr));
if (ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) {perror("ioctl SIOCSIFADDR");close(sockfd);return -1;}
printf("New IP address of %s set to: %s\n", INTERFACE, inet_ntoa(new_addr.sin_addr));
close(sockfd);return 0;
}
常见的请求代码:
不同的设备驱动程序会定义自己的请求代码,以下是一些常见的请求代码示例:
-
串口设备:
-
TCGETS
:获取当前终端的配置。 -
TCSETS
:设置终端的配置。 -
TIOCGWINSZ
:获取窗口大小。 -
TIOCSWINSZ
:设置窗口大小。
-
-
网络设备:
-
SIOCGIFADDR
:获取网络接口的 IP 地址。 -
SIOCSIFADDR
:设置网络接口的 IP 地址。 -
SIOCGIFFLAGS
:获取网络接口的标志。 -
SIOCSIFFLAGS
:设置网络接口的标志。
-
-
文件系统:
-
BLKGETSIZE
:获取块设备的大小。 -
BLKFLSBUF
:刷新块设备的缓冲区。
-
注意事项:
-
设备依赖性:
ioctl
的请求代码和参数通常依赖于具体的设备驱动程序,因此在使用时需要参考设备的文档或头文件。 -
安全性:由于
ioctl
操作通常涉及低级硬件控制,不当使用可能导致系统不稳定或设备损坏。
2、文件描述符进行各种控制操作
在 Linux 系统中,fcntl
(File Control)函数用于对文件描述符进行各种控制操作。它提供了比标准 I/O 函数更灵活的文件描述符管理功能,可以用于设置文件状态标志、获取和修改文件描述符的属性等。
函数原型:
int fcntl(int fd, int cmd, ...);
参数说明:
-
fd
:文件描述符,表示要操作的文件或设备。通常是通过open
函数打开文件或设备后获得的。 -
cmd
:指定要执行的命令类型。根据不同的命令类型,可能需要额外的参数。 -
...
:可选参数,具体取决于命令类型。它可以是一个整数、一个指针或其他类型的数据,用于传递或接收与命令相关的数据。
常见命令:
fcntl
支持多种命令类型,以下是一些常用的命令及其用途:
-
F_DUPFD
:-
创建一个指向同一文件的新文件描述符,新文件描述符的值大于或等于
arg
。 -
参数:
arg
(整数)。 -
返回值:新文件描述符。
-
-
F_GETFD
:-
获取文件描述符标志(
FD_CLOEXEC
)。 -
参数:无。
-
返回值:文件描述符标志。
-
-
F_SETFD
:-
设置文件描述符标志(
FD_CLOEXEC
)。 -
参数:标志值(整数)。
-
返回值:0(成功)或 -1(失败)。
-
-
F_GETFL
:-
获取文件状态标志(如
O_RDONLY
、O_WRONLY
、O_NONBLOCK
等)。 -
参数:无。
-
返回值:文件状态标志。
-
-
F_SETFL
:-
设置文件状态标志。
-
参数:标志值(整数)。
-
返回值:0(成功)或 -1(失败)。
-
-
F_GETLK``、
F_SETLK、
F_SETLKW`:-
用于文件锁定操作。
-
参数:
struct flock *lock
,表示锁定信息。 -
返回值:0(成功)或 -1(失败)。
-
返回值:
-
成功时返回 0 或新文件描述符(如
F_DUPFD
)。 -
失败时返回 -1,并设置
errno
以指示错误原因。
示例代码 1:
设置文件描述符为非阻塞模式
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {int fd = open("/dev/null", O_RDWR);if (fd == -1) {perror("open");return -1;}
int flags = fcntl(fd, F_GETFL); // 获取当前文件状态标志if (flags == -1) {perror("fcntl F_GETFL");close(fd);return -1;}
flags |= O_NONBLOCK; // 设置非阻塞模式if (fcntl(fd, F_SETFL, flags) == -1) {perror("fcntl F_SETFL");close(fd);return -1;}
printf("File descriptor set to non-blocking mode\n");close(fd);return 0;
}
示例代码 2:
复制文件描述符
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {int fd = open("/dev/null", O_RDWR);if (fd == -1) {perror("open");return -1;}
int new_fd = fcntl(fd, F_DUPFD, 10); // 创建一个新文件描述符,值大于或等于 10if (new_fd == -1) {perror("fcntl F_DUPFD");close(fd);return -1;}
printf("New file descriptor: %d\n", new_fd);close(fd);close(new_fd);return 0;
}
示例代码 3:
设置文件描述符标志(FD_CLOEXEC
)
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {int fd = open("/dev/null", O_RDWR);if (fd == -1) {perror("open");return -1;}
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {perror("fcntl F_SETFD");close(fd);return -1;}
printf("File descriptor set to close-on-exec\n");close(fd);return 0;
}
常见用途:
-
设置文件描述符为非阻塞模式:
-
通过设置文件状态标志
O_NONBLOCK
,可以将文件描述符设置为非阻塞模式,这在处理 I/O 操作时非常有用,尤其是在多线程或异步编程中。
-
-
复制文件描述符:
-
使用
F_DUPFD
命令可以创建一个指向同一文件的新文件描述符,这在需要将文件描述符传递给其他进程或线程时非常有用。
-
-
设置文件描述符标志:
-
通过设置文件描述符标志
FD_CLOEXEC
,可以确保文件描述符在执行exec
系统调用时不会被继承,从而提高程序的安全性。
-
-
文件锁定:
-
使用
F_GETLK
、F_SETLK
和F_SETLKW
命令可以实现文件锁定,防止多个进程同时对同一个文件进行写操作,从而避免数据竞争。
-
3、多路复用 I/O ,让进程监视多个文件描述符1
在 Linux 系统中,select
函数是一种用于多路复用 I/O 的系统调用,它可以让进程监视多个文件描述符,当其中任意一个文件描述符就绪(可读、可写或有异常条件待处理)时,select
函数就会返回。
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数说明:
-
nfds
:是需要监视的文件描述符的最大值加 1。select
函数会监视从 0 到nfds - 1
的所有文件描述符。 -
readfds
:指向一个fd_set
类型的指针,表示需要监视可读性的文件描述符集合。如果某个文件描述符对应的位被设置为 1,则表示该文件描述符需要被监视是否可读。 -
writefds
:指向一个fd_set
类型的指针,表示需要监视可写的文件描述符集合。如果某个文件描述符对应的位被设置为 1,则表示该文件描述符需要被监视是否可写。 -
exceptfds
:指向一个fd_set
类型的指针,表示需要监视异常条件的文件描述符集合。如果某个文件描述符对应的位被设置为 1,则表示该文件描述符需要被监视是否有异常条件发生。 -
timeout
:指向一个struct timeval
类型的指针,用于指定select
函数的超时时间。如果设置为NULL
,则select
函数会阻塞,直到有文件描述符就绪为止;如果设置为一个非NULL
的指针,并且指针所指向的struct timeval
的值为 0,则select
函数会立即返回,不会阻塞。
返回值:
-
如果有文件描述符就绪,返回值为就绪的文件描述符数量。
-
如果超时返回 0。
-
如果发生错误返回 -1,并设置相应的错误码。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd; // 假设这是一个已经创建好的套接字文件描述符fd_set readfds;struct timeval timeout;char buffer[1024];int maxfd;// 初始化文件描述符集合FD_ZERO(&readfds);FD_SET(STDIN_FILENO, &readfds); // 添加标准输入文件描述符FD_SET(sockfd, &readfds); // 添加套接字文件描述符maxfd = (sockfd > STDIN_FILENO ? sockfd : STDIN_FILENO) + 1;// 设置超时时间timeout.tv_sec = 5; // 5 秒timeout.tv_usec = 0;int ret = select(maxfd, &readfds, NULL, NULL, &timeout);if (ret == -1) {perror("select error");exit(EXIT_FAILURE);} else if (ret == 0) {printf("Timeout occurred! No data after 5 seconds.\n");} else {if (FD_ISSET(STDIN_FILENO, &readfds)) {// 标准输入可读if (read(STDIN_FILENO, buffer, sizeof(buffer)) > 0) {printf("Data from stdin: %s\n", buffer);}}if (FD_ISSET(sockfd, &readfds)) {// 套接字可读if (recv(sockfd, buffer, sizeof(buffer), 0) > 0) {printf("Data from socket: %s\n", buffer);}}}return 0;
}
4、多路复用 I/O ,监视多个文件描述符2
在 Linux 系统中,poll
函数也是一种用于多路复用 I/O 的系统调用,它用于监视多个文件描述符,当其中任意一个文件描述符就绪(可读、可写或有异常条件待处理)时,poll
函数就会返回。与 select
函数相比,poll
函数在某些方面有其独特的优势。
函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数说明:
-
fds
:指向一个struct pollfd
类型的数组,每个数组元素表示一个需要监视的文件描述符及其状态。struct pollfd
的定义如下:struct pollfd {int fd; // 文件描述符short events; // 请求监视的事件short revents; // 实际发生的事件 };
-
fd
:需要监视的文件描述符。 -
events
:指定需要监视的事件类型,可以是以下值的组合:-
POLLIN
:数据可读。 -
POLLOUT
:数据可写。 -
POLLPRI
:高优先级数据可读。 -
POLLERR
:发生错误。 -
POLLHUP
:挂起。 -
POLLNVAL
:无效的文件描述符。
-
-
revents
:poll
函数返回时,该字段会包含实际发生的事件。
-
-
nfds
:fds
数组中的元素数量,即需要监视的文件描述符数量。 -
timeout
:指定poll
函数的超时时间,单位为毫秒。如果设置为-1
,则poll
函数会阻塞,直到有文件描述符就绪为止;如果设置为0
,则poll
函数会立即返回,不会阻塞。
返回值:
-
如果有文件描述符就绪,返回值为就绪的文件描述符数量。
-
如果超时返回 0。
-
如果发生错误返回 -1,并设置相应的错误码。
示例代码:
使用 poll
函数监视标准输入和一个套接字文件描述符是否可读
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd; // 假设这是一个已经创建好的套接字文件描述符struct pollfd fds[2];int ret;char buffer[1024];// 初始化 pollfd 数组fds[0].fd = STDIN_FILENO; // 标准输入文件描述符fds[0].events = POLLIN;fds[1].fd = sockfd; // 套接字文件描述符fds[1].events = POLLIN;// 设置超时时间int timeout = 5000; // 5 秒ret = poll(fds, 2, timeout);if (ret == -1) {perror("poll error");exit(EXIT_FAILURE);} else if (ret == 0) {printf("Timeout occurred! No data after 5 seconds.\n");} else {if (fds[0].revents & POLLIN) {// 标准输入可读if (read(STDIN_FILENO, buffer, sizeof(buffer)) > 0) {printf("Data from stdin: %s\n", buffer);}}if (fds[1].revents & POLLIN) {// 套接字可读if (recv(sockfd, buffer, sizeof(buffer), 0) > 0) {printf("Data from socket: %s\n", buffer);}}}return 0;
}
5、高效的 I/O 多路复用机制
在 Linux 系统中,epoll
是一种高效的 I/O 多路复用机制,用于同时监视多个文件描述符的 I/O 事件。它通过内核态的数据结构优化和事件驱动机制,解决了传统 select
和 poll
的性能瓶颈。
主要特点:
-
高效的数据结构:
-
epoll
在内核中使用红黑树来管理所有需要监视的文件描述符,增删改操作的时间复杂度为 O(logn),相比select
和poll
的线性扫描方式,效率更高。 -
内核维护了一个就绪事件链表,当文件描述符有事件发生时,内核会将其加入到这个链表中。
-
-
事件驱动机制:
-
epoll
使用回调函数机制,当文件描述符有事件发生时,内核会自动将其加入到就绪事件链表中,用户调用epoll_wait
时,直接返回就绪的文件描述符,无需像select
和poll
那样遍历整个文件描述符集合。
-
-
支持水平触发和边缘触发:
-
水平触发(LT):默认模式,只要文件描述符仍然处于就绪状态,就会一直触发事件。
-
边缘触发(ET):只有当文件描述符的状态从非就绪变为就绪时才会触发事件,通常与非阻塞 I/O 搭配使用。
-
主要函数:
-
epoll_create
: 创建一个epoll
实例,返回一个文件描述符,用于后续操作。从 Linux 2.6.8 开始,size
参数被忽略,但必须大于 0。int epoll_create(int size);
-
epoll_ctl
: 用于向epoll
实例中添加、修改或删除需要监视的文件描述符。int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-
epfd
:epoll_create
返回的文件描述符。 -
op
:操作类型,可以是EPOLL_CTL_ADD
(添加)、EPOLL_CTL_MOD
(修改)或EPOLL_CTL_DEL
(删除)。 -
fd
:需要监视的文件描述符。 -
event
:指定需要监视的事件类型,如EPOLLIN
(可读)、EPOLLOUT
(可写)等。
-
-
epoll_wait
: 等待epoll
实例中的事件发生,返回就绪的文件描述符数量。int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
-
epfd
:epoll_create
返回的文件描述符。 -
events
:存储就绪事件的数组。 -
maxevents
:events
数组的最大容量。 -
timeout
:超时时间(毫秒),-1
表示永久阻塞,0
表示立即返回。
-
示例代码:
监视标准输入和一个套接字文件描述符是否可读:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <string.h>#define MAX_EVENTS 10int main() {int epfd = epoll_create(2); // 创建 epoll 实例if (epfd == -1) {perror("epoll_create");exit(EXIT_FAILURE);}struct epoll_event ev, events[MAX_EVENTS];ev.events = EPOLLIN; // 监听可读事件ev.data.fd = STDIN_FILENO; // 标准输入文件描述符if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {perror("epoll_ctl");close(epfd);exit(EXIT_FAILURE);}int sockfd; // 假设这是一个已经创建好的套接字文件描述符ev.data.fd = sockfd;if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {perror("epoll_ctl");close(epfd);exit(EXIT_FAILURE);}while (1) {int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); // 等待事件if (nfds == -1) {perror("epoll_wait");close(epfd);exit(EXIT_FAILURE);}for (int n = 0; n < nfds; ++n) {if (events[n].data.fd == STDIN_FILENO) {char buffer[1024];if (read(STDIN_FILENO, buffer, sizeof(buffer)) > 0) {printf("Data from stdin: %s\n", buffer);}} else if (events[n].data.fd == sockfd) {char buffer[1024];if (recv(sockfd, buffer, sizeof(buffer), 0) > 0) {printf("Data from socket: %s\n", buffer);}}}}close(epfd); // 关闭 epoll 实例return 0;
}
参考资源:https://download.csdn.net/download/2403_82436914/90631882?spm=1001.2014.3001.5503