这篇文章分享下 Linux 系统编程中用于设备专用控制的核心系统调用: ioctl 函数。ioctl(输入输出控制)是一个系统调用,常用于 Linux 操作系统。它允许用户程序通过文件描述符与设备驱动程序进行交互,执行特定于设备的控制操作。在许多设备(如网络接口、串口、终端等)中,标准的输入输出操作(例如 read 和 write)无法满足所有需求。但是 ioctl 提供了一种处理这种方法的机制,通过它可以进行更复杂的控制和信息查询。#include<sys/ioctl.h>intioctl(int fd, unsignedlong request, ... /* argp */);
- fd:已打开的设备文件描述符(如 /dev/tty、/dev/sda)。
- request:设备特定的控制命令(如 TIOCGWINSZ 获取终端大小)。
- argp:指向数据结构的指针或整数值,内容由 request 决定。
- 成功时:通常返回 0 或非负值(具体由设备驱动定义)。
- 失败时:返回 -1,并设置 errno(如 ENOTTY 表示 fd 不是字符设备)。
当 ioctl 调用失败时,errno 会被设置为相应的错误代码。常见的错误包括:- ENOTTY: 尝试对不支持 ioctl 的设备进行控制。
- 设备控制: 控制硬件设备的行为,如设置串口的波特率、获取网络接口的信息等。
- 状态查询: 查询设备的状态或属性,如获取网络接口的 MAC 地址、获取终端的窗口大小等。
- 修改设置: 修改设备的配置或属性,如设置音频设备的音量、调整图形设备的分辨率等。
- devname: 网络设备的名称(例如 "eth0" 或 "wlan0")。
- 返回值: 函数返回一个整数,通常 0 表示成功,-1 表示错误。
#include<stdio.h>#include<string.h>#include<sys/ioctl.h>#include<net/if.h>#include<arpa/inet.h>#include<unistd.h>intget_mac_address(char *devname ,char *buf ,int size){ int sock_mac; struct ifreq ifr_mac; sock_mac = socket(AF_INET, SOCK_STREAM, 0); if( sock_mac < 0){ return -1; } memset(&ifr_mac,0,sizeof(ifr_mac)); strncpy(ifr_mac.ifr_name, devname, sizeof(ifr_mac.ifr_name)-1); if(ioctl(sock_mac, SIOCGIFHWADDR, &ifr_mac) < 0){ goto err; } snprintf(buf, size,"%02X:%02X:%02X:%02X:%02X:%02X", (unsigned char)ifr_mac.ifr_hwaddr.sa_data[0], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[1], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[2], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[3], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[4], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[5]); close(sock_mac); return 0; err: close(sock_mac); return -1;}
- 使用方法: 将 struct ifreq 传递给 ioctl
- 返回数据: IPv4 地址存储在 参数 buf 中
#include<stdio.h>#include<string.h>#include<sys/ioctl.h>#include<net/if.h>#include<arpa/inet.h>#include<unistd.h>voidget_ip_address(constchar *devname, char *buf ,int size){ int sock; struct ifreq ifr; // 创建套接字 sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return; } // 清空结构体并设置接口名称 memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IFNAMSIZ - 1); // 获取 IP 地址 if (ioctl(sock, SIOCGIFADDR, &ifr) == -1) { perror("ioctl"); close(sock); return; } struct sockaddr_in *ipaddr = (struct sockaddr_in *)&ifr.ifr_addr; snprintf(buf, size,"%s", inet_ntoa(ipaddr->sin_addr)); close(sock);}
- 使用方法: 将 struct ifreq 传递给 ioctl。
- 返回数据: MTU 值存储在 ifr_mtu 字段。
#include<stdio.h>#include<string.h>#include<sys/ioctl.h>#include<net/if.h>#include<unistd.h>intget_mtu(constchar *devname){ int sock; struct ifreq ifr; // 创建套接字 sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return -1; } // 清空结构体并设置接口名称 memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IFNAMSIZ - 1); // 获取 MTU if (ioctl(sock, SIOCGIFMTU, &ifr) == -1) { perror("ioctl"); close(sock); return -1; } printf("MTU of %s: %d\n", devname, ifr.ifr_mtu); close(sock); return ifr.ifr_mtu;}
- 使用方法: 修改 struct ifreq 中的 ifr_flags 字段,然后通过 ioctl 发送。
- 注意: 需要使用 SIOCGIFFLAGS 先获取当前的标志,再进行修改。
#include<stdio.h>#include<string.h>#include<sys/ioctl.h>#include<net/if.h>#include<unistd.h>voidset_interface_up(constchar *devname){ int sock; struct ifreq ifr; // 创建套接字 sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return; } // 清空结构体并设置接口名称 memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IFNAMSIZ - 1); // 获取当前接口状态 if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) { perror("ioctl"); close(sock); return; } // 设置接口为 UP ifr.ifr_flags |= IFF_UP; // 启用接口 // 更新接口状态 if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) { perror("ioctl"); close(sock); return; } printf("Interface %s is now up.\n", devname); close(sock);}
- 使用方法: 将 struct ifreq 传递给 ioctl。
#include<stdio.h>#include<string.h>#include<sys/ioctl.h>#include<net/if.h>#include<arpa/inet.h>#include<unistd.h>voidget_netmask(constchar *devname, char *buf, int size){ int sock; struct ifreq ifr; // 创建套接字 sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return; } // 清空结构体并设置接口名称 memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IFNAMSIZ - 1); // 获取网络掩码 if (ioctl(sock, SIOCGIFNETMASK, &ifr) == -1) { perror("ioctl"); close(sock); return; } struct sockaddr_in *netmask = (struct sockaddr_in *)&ifr.ifr_netmask; snprintf(buf, size, "%s\n", inet_ntoa(netmask->sin_addr)); close(sock);}
- 使用方法: 将 struct ifreq 传递给 ioctl。
#include<stdio.h>#include<string.h>#include<sys/ioctl.h>#include<net/if.h>#include<arpa/inet.h>#include<unistd.h>voidget_broadcast_address(constchar *devname, char *buf, int size){ int sock; struct ifreq ifr; // 创建套接字 sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return; } // 清空结构体并设置接口名称 memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IFNAMSIZ - 1); // 获取广播地址 if (ioctl(sock, SIOCGIFBRDADDR, &ifr) == -1) { perror("ioctl"); close(sock); return; } struct sockaddr_in *broadaddr = (struct sockaddr_in *)&ifr.ifr_broadaddr; snprintf(buf, size, "%s", inet_ntoa(broadaddr->sin_addr)); close(sock);}
#include<stdio.h>#include<sys/ioctl.h>#include<unistd.h>voidget_term_size(){ struct winsize ws; // 终端窗口大小结构体 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) { perror("ioctl failed"); return 1; } printf("终端窗口大小:%d 行 x %d 列\n", ws.ws_row, ws.ws_col); return 0;}
上面是一些常见的 ioctl 操作都是小编在实际工作中用到过的。每个请求代码都有特定的结构体和字段用于获取或设置网络接口的各种信息。小伙伴们在使用这些请求代码时,请确保使用正确的头文件,并了解相应的结构和字段,以便正确处理数据。小伙伴们有任何问题,可以随时评论区留言或者私信小编,咱们一起学习,一起进步~