一. 简介

本文来简单学习一下,嵌入式开发中,fcntl()函数和 ioctrl()函数使用过程中的区别。

二. fcntl()和 ioctrl()函数区别

1. 核心区别

fcntl()ioctl() 是 Linux 系统中两个重要的系统调用,它们都用于对文件或设备进行控制操作,但设计目的、使用场景和功能有显著区别。

核心区别对比:

维度 fcntl() ioctl()
设计目标 标准化接口,处理通用文件描述符操作 灵活接口,处理设备特定的控制操作
适用范围 所有文件描述符(常规文件、管道、设备等) 主要用于设备文件(字符设备、块设备、网络设备等)
命令标准化 命令由系统统一定义(如 F_GETFLF_SETFL 命令由设备驱动自定义,通常与设备类型强相关
参数风格 固定参数格式:int fcntl(int fd, int cmd, ...) 可变参数格式:int ioctl(int fd, unsigned long cmd, ...)
可移植性 高(系统调用和命令跨平台统一) 低(设备驱动自定义命令,跨平台差异大)

2. 具体分析

(1) fcntl():通用文件描述符控制

fcntl(file control)的核心是对文件描述符进行标准化的通用操作,其命令由系统统一定义,适用于所有类型的文件描述符(包括普通文件、管道、套接字、设备文件等)。

主要功能

  • 文件状态标志操作:获取 / 设置文件状态标志(如 O_NONBLOCK 非阻塞模式、O_APPEND 追加模式),对应命令 F_GETFL(获取)和 F_SETFL(设置)。
int flags = fcntl(fd, F_GETFL, 0);       // 获取当前标志
fcntl(fd, F_SETFL, flags | O_NONBLOCK);  // 设置非阻塞模式
  • 文件锁管理:实现记录锁(record locking),如 F_LOCK(加锁)、F_UNLOCK(解锁),用于多进程同步。例如:
    for (int i = 0; i < 64; i++) {   
        char dev[64];
        sprintf(dev, "/dev/hidraw%d", i);
        fd = open(dev, O_RDWR);
        //成功usb打开设备
        if(fd > 0)
        {
            //Get Raw Info
            res = ioctl(fd, HIDIOCGRAWINFO, &info);
            if (res < 0)
            {
                perror("HIDIOCGRAWINFO");
            }
            else
            {
                sprintf(tmp,"0x%04hx 0x%04hx", info.vendor, info.product);
                if(strstr(tmp,"0x0123") != NULL && strstr(tmp,"0xa1b2") != NULL)
                {
                    //锁的类型                                                
                    lock.l_type = F_WRLCK;
                    //加锁整个文件
                    lock.l_whence = SEEK_SET;
                    lock.l_start = 0;
                    lock.l_len = 0;
                    //对设备文件描述符进行加锁
                    if(fcntl(fd, F_SETLK, &lock) < 0)
                    {
                        perror("fcntl");
                        if(fcntl(fd,F_GETLK, &lock) < 0)
                        {
                            perror("fcntl");
                            printf("lock.l_type:%d.\n",lock.l_type);
                            return  -1;
                        }
                        if(lock.l_type != F_UNLCK)
                        {
                            printf("lock.l_type:%d.\n",lock.l_type);
                            return -2;
                        }
                        return -1;
                    }
                    return(fd);                                        
                }    
            }
            close(fd);
        }
    }
  • 复制文件描述符F_DUPFD 复制文件描述符,类似 dup() 但更灵活。
  • 信号驱动 I/O:设置文件描述符就绪时的信号通知(如 F_SETOWN 指定接收信号的进程)。

特点

  • 命令是标准化的(由 POSIX 定义),跨系统(如 Linux、macOS)行为一致。
  • 操作对象是 “文件描述符” 本身的属性,与底层设备类型无关。

(2) ioctl():设备特定控制

ioctl(I/O control)的核心是针对特定设备的自定义控制操作,其命令由设备驱动程序定义,主要用于硬件设备(如串口、网卡、块设备等)的特殊配置。

主要功能

  • 设备参数配置:如串口的波特率(TIOCGSERIAL 获取、TIOCSSERIAL 设置)、网卡的 MAC 地址配置。
struct termios tty;
ioctl(fd, TCGETS, &tty);  // 获取串口配置
tty.c_cflag = B9600 | CS8 | CLOCAL | CREAD;  // 设置波特率9600、8位数据位
ioctl(fd, TCSETS, &tty);  // 应用配置
  • 设备状态查询:如获取磁盘分区表、检查摄像头是否正在录制。
  • 特殊 I/O 操作:如控制 LED 闪烁、触发设备重置、读取传感器原始数据等。

特点

  • 命令是 “设备相关” 的,通常由驱动开发者自定义(需避免与系统保留命令冲突)。
  • 接口灵活但缺乏标准化,不同设备的 ioctl 命令和参数格式可能完全不同(如串口和网卡的 ioctl 命令无交集)。
  • 可移植性差,同一功能在不同系统或设备上的 ioctl 实现可能不同。

三. 总结

使用 fcntl()函数,ioctrl()函数,这两个如何选择?

  • 用 fcntl():当需要操作通用文件描述符属性(如非阻塞模式、文件锁),且希望代码跨平台兼容时。
  • 用 ioctl():当需要与特定硬件设备交互(如配置设备参数、获取设备特有状态),且接受代码与设备驱动强耦合时。

fcntl()函数 是 “通用标准化接口”,专注于文件描述符的共性操作;

ioctl() 函数是 “设备专用接口”,专注于设备的个性化控制。

两者互补:fcntl()函数 保证了文件操作的统一性和可移植性,ioctl()函数 则为设备驱动提供了灵活扩展的空间。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐