Linux创建sysfs属性节点 - DEVICE_ATTR宏、device_create_file()、sysfs_create_group()

news2024/9/20 20:44:16

目录

简介:

一、DEVICE_ATTR介绍

1、DEVICE_ATTR宏

1.1 参数说明

1.2 调用方法

二、sysfs创建属性文件

1、创建一个sysfs属性文件

1.1 device_create_file()函数

1.2 device_create_file()实例

2、创建多个sysfs属性文件

2.1 sysfs_create_group()函数

2.2 sysfs_create_group()实例


简介:

在Linux驱动调试时,常常需要添加属性文件,sysfs属性节点可以实现用户空间与硬件交互,如:设置GPIO管脚电平、控制驱动等功能。下面介绍如何创建sysfs属性节点。

一、DEVICE_ATTR介绍


1、DEVICE_ATTR宏


DEVICE_ATTR 宏在linux/device.h中有如下定义:

/* 路径:linux/device.h */
#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

struct device_attribute {
    struct attribute	attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                    char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                     const char *buf, size_t count);
};

/* 路径:include/linux/sysfs.h */
#define __ATTR(_name, _mode, _show, _store) {				\
	.attr = {.name = __stringify(_name),				\
		 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },		\
	.show	= _show,						\
	.store	= _store,						\
}

DEVICE_ATTR 宏用来定义一个 struct device_attribute 结构体,并对各成员初始化。

1.1 参数说明

DEVICE_ATTR(_name, _mode, _show, _store)
  • _name:名称,也就是将在sysfs中生成的文件名称;
  • _mode:属性文件的权限mode,与普通文件相同,UGO的格式。只读0444,只写0222,或者读写都行的0666;
  • _show:显示函数,cat该文件时,此函数被调用;
  • _store:写函数,echo内容到该文件时,此函数被调用;

1.2 调用方法

static DEVICE_ATTR(demo, 0664, demo_show, demo_store);

demo_show、demo_store函数由我们自己定义,展开后

struct device_attribute dev_attr_demo = {
	.attr = {.name = __stringify(demo),
	.mode = VERIFY_OCTAL_PERMISSIONS(0664) },
	.show	= demo_show,
	.store	= demo_store,
}

调用 DEVICE_ATTR 后,就将 device_attribute 结构体初始化完成了。还需要使用 device_create_file() sysfs_create_group() 将属性文件加入sysfs文件系统中。

二、sysfs创建属性文件


1、创建一个sysfs属性文件


1.1 device_create_file()函数

DEVICE_ATTR 宏创建 device_attribute 结构体后,调用 device_create_file() 将属性文件加入sysfs文件系统中,会在 /sys/class/xxx 子目录下生成一个属性文件。

/* 路径:drivers/base/core.c */
int device_create_file(struct device *dev,
                       const struct device_attribute *attr)
{
    int error = 0;

    if (dev) {
        WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
             "Attribute %s: write permission without 'store'\n",
             attr->attr.name);
        WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
             "Attribute %s: read permission without 'show'\n",
             attr->attr.name);
        error = sysfs_create_file(&dev->kobj, &attr->attr);
    }

    return error;
}
EXPORT_SYMBOL_GPL(device_create_file);

这种方式一次只能创建一个属性节点。

1.2 device_create_file()实例

使用 device_create_file() 函数时要引用 device_create 创建设备时返回的device*指针,作用是在/sys/class/下创建一个属性文件,从而通过对这个属性文件进行读写就能完成对应的数据操作。

实例代码:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

typedef struct
{
	dev_t devid; 			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
	struct cdev cdev;   	/* cdev */
	struct class *class;	/* 类 */
	struct device *device;  /* 设备 */
}CHARDEV_HANDLE_T;

#define CDEV_CNT		1		  		/* 设备号个数 */
#define CDEV_NAME		"my_leddrv"		/* 名字 */

static CHARDEV_HANDLE_T stMyled = {0};

static int led_drv_open(struct inode *node, struct file *file)
{
	//filp->private_data = &cdev_data;  /* 设置私有数据 */
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 1;
}

static int led_drv_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_drv_fops =
{
    .owner = THIS_MODULE,
    .open  = led_drv_open,
    .read  = led_drv_read,
    .write = led_drv_write,
    .release = led_drv_release,
};

ssize_t para_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return sprintf(buf, "hello world!\n");
}

ssize_t para_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    printk(KERN_INFO "%s\n", buf);
    return count;
}

static DEVICE_ATTR(para, 0664, para_show, para_store);

/* 入口函数 */
static int __init led_init(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (stMyled.major) { /* 定义了设备号 */
        stMyled.devid = MKDEV(stMyled.major, 0);
        register_chrdev_region(stMyled.devid, CDEV_CNT, CDEV_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&stMyled.devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
        stMyled.major = MAJOR(stMyled.devid); /* 获取分配号的主设备号 */
        stMyled.minor = MINOR(stMyled.devid); /* 获取分配号的次设备号 */
    }
    printk("major=%d, minor=%d\r\n", stMyled.major, stMyled.minor);

    /* 2、初始化 cdev */
    stMyled.cdev.owner = THIS_MODULE;
    cdev_init(&stMyled.cdev, &led_drv_fops);				//file_operations

    /* 3、添加一个 cdev */
    cdev_add(&stMyled.cdev, stMyled.devid, CDEV_CNT);

    /* 4、创建类 */
    stMyled.class = class_create(THIS_MODULE, CDEV_NAME);	///sys/class/目录下会创建一个新的文件夹
    if (IS_ERR(stMyled.class)) {
        return PTR_ERR(stMyled.class);
    }

    /* 5、创建设备 */
    stMyled.device = device_create(stMyled.class, NULL, stMyled.devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
    if (IS_ERR(stMyled.device)) {
        return PTR_ERR(stMyled.device);
    }
	
	/* 6、将属性文件加入sysfs文件系统中 */
    device_create_file(stMyled.device, &dev_attr_para);
	
    return 0;
}

/* 出口函数 */
static void __exit led_exit(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	
    /* 注销字符设备驱动 */
    cdev_del(&stMyled.cdev); /* 删除 cdev */
    unregister_chrdev_region(stMyled.devid, CDEV_CNT); /* 注销 */

    device_destroy(stMyled.class, stMyled.devid);
    class_destroy(stMyled.class);
	
	/* 移除sysfs中的device属性节点 */
	//device_remove_file(stMyled.device, &dev_attr_para);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");

Makefile编译,生成 .ko 文件

KERN_DIR = /home/linux-imx-rel_imx_4.1.15_2.1.0

all:
	make -C $(KERN_DIR) M=`pwd` modules

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m   += chardev_drv.o

insmod加载 .ko 驱动后,生成 /sys/class/my_leddrv/my_leddrv/ 下有para属性节点。cat命令会调用 .show 函数,echo 会调用 .store 函数。测试结果如下:

# cat /sys/class/my_leddrv/my_leddrv/para

# echo "DEVICE_ATTR test" > /sys/class/my_leddrv/my_leddrv/para

2、创建多个sysfs属性文件


2.1 sysfs_create_group()函数

对于多个属性文件的添加,我们可以定义属性组,然后将这个属性组使用 sysfs_create_group() 添加进sysfs文件系统中。

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)

2.2 sysfs_create_group()实例

关键步骤:

static DEVICE_ATTR(para1, 0664, para_show, para_store);
static DEVICE_ATTR(para2, 0664, para_show, para_store);
static DEVICE_ATTR(para3, 0664, para_show, para_store);

static struct attribute *para_attribute[] =
{
    &dev_attr_para1.attr,
    &dev_attr_para2.attr,
    &dev_attr_para3.attr,
    NULL,
};

static struct attribute_group para_attribute_group =
{
    .attrs = para_attribute,
};

sysfs_create_group(&myled.device->kobj, &para_attribute_group);

上面关键步骤我为了简单各个属性文件的show函数和store函数都一样,大家可以根据实际情况自行分别定义。

示例如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

typedef struct
{
	dev_t devid; 			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
	struct cdev cdev;   	/* cdev */
	struct class *class;	/* 类 */
	struct device *device;  /* 设备 */
}CHARDEV_HANDLE_T;

#define CDEV_CNT		1		  		/* 设备号个数 */
#define CDEV_NAME		"my_leddrv"		/* 名字 */

static CHARDEV_HANDLE_T stMyled = {0};

static int led_drv_open(struct inode *node, struct file *file)
{
	//filp->private_data = &cdev_data;  /* 设置私有数据 */
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

static int led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 1;
}

static int led_drv_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_drv_fops =
{
    .owner = THIS_MODULE,
    .open  = led_drv_open,
    .read  = led_drv_read,
    .write = led_drv_write,
    .release = led_drv_release,
};

ssize_t para_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    return sprintf(buf, "hello world!\n");
}

ssize_t para_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    printk(KERN_INFO "%s\n", buf);
    return count;
}

static DEVICE_ATTR(para1, 0664, para_show, para_store);
static DEVICE_ATTR(para2, 0664, para_show, para_store);
static DEVICE_ATTR(para3, 0664, para_show, para_store);

static struct attribute *para_attribute[] =
{
    &dev_attr_para1.attr,
    &dev_attr_para2.attr,
    &dev_attr_para3.attr,
    NULL,
};

static struct attribute_group para_attribute_group =
{
    .attrs = para_attribute,
};

/* 入口函数 */
static int __init led_init(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (stMyled.major) { /* 定义了设备号 */
        stMyled.devid = MKDEV(stMyled.major, 0);
        register_chrdev_region(stMyled.devid, CDEV_CNT, CDEV_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&stMyled.devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
        stMyled.major = MAJOR(stMyled.devid); /* 获取分配号的主设备号 */
        stMyled.minor = MINOR(stMyled.devid); /* 获取分配号的次设备号 */
    }
    printk("major=%d, minor=%d\r\n", stMyled.major, stMyled.minor);

    /* 2、初始化 cdev */
    stMyled.cdev.owner = THIS_MODULE;
    cdev_init(&stMyled.cdev, &led_drv_fops);				//file_operations

    /* 3、添加一个 cdev */
    cdev_add(&stMyled.cdev, stMyled.devid, CDEV_CNT);

    /* 4、创建类 */
    stMyled.class = class_create(THIS_MODULE, CDEV_NAME);	///sys/class/目录下会创建一个新的文件夹
    if (IS_ERR(stMyled.class)) {
        return PTR_ERR(stMyled.class);
    }

    /* 5、创建设备 */
    stMyled.device = device_create(stMyled.class, NULL, stMyled.devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
    if (IS_ERR(stMyled.device)) {
        return PTR_ERR(stMyled.device);
    }
	
	/* 6、将属性文件加入sysfs文件系统中 */
    sysfs_create_group(&stMyled.device->kobj, &para_attribute_group);
	
    return 0;
}

/* 出口函数 */
static void __exit led_exit(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	
    /* 注销字符设备驱动 */
    cdev_del(&stMyled.cdev); /* 删除 cdev */
    unregister_chrdev_region(stMyled.devid, CDEV_CNT); /* 注销 */

    device_destroy(stMyled.class, stMyled.devid);
    class_destroy(stMyled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");

加载 .ko 文件后,在 /sys/class/my_leddrv/my_leddrv 目录下多了 para1、para2、para3。

使用 cat、echo命令测试:

# cat /sys/class/my_leddrv/my_leddrv/para

# echo 1 > /sys/class/my_leddrv/my_leddrv/para


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2094922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI少女/HS2甜心选择2 仿崩坏3卡系列全合集打包

内含AI少女/甜心选择2 仿崩坏3系列全合集打包共11张 内含&#xff1a;月魄装 幽兰黛尔幽兰黛尔薪炎之律者青鸟之庭帕朵菲莉丝雷电芽衣校服布洛妮娅八重樱 冰海琴音爱衣 悠闲旋律爱莉希雅EVA 明日香。 下载地址&#xff1a;https://www.51888w.com/241.html 部分演示图&#…

java构建工具-maven的复习笔记【适用于复习或者初步了解】

&#x1f939;‍♀️潜意识起点&#xff1a;个人主页 &#x1f399;座右铭&#xff1a;得之坦然&#xff0c;失之淡然。 &#x1f48e;擅长领域&#xff1a;前端 是的&#xff0c;我需要您的&#xff1a; &#x1f9e1;点赞❤️关注&#x1f499;收藏&#x1f49b; 是我持…

鸿蒙UIAbility组件进阶

鸿蒙UIAbility组件进阶 AbilityStage基本概念生命周期使用方式 UIAbility组件间交互启动应用内的UIAbility启动应用内的UIAbility并获取返回结果启动UIAbility的指定页面调用方目标UIAbility冷启动目标UIAbility热启动 AbilityStage 基本概念 在了解AbilityStage之前&#xf…

Promise 工具箱:手写实现静态方法的完全指南

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 Promise有很多静态方法&#xff0c;本文就来分享下如何实现这些静态方法。 目录 …

Markdown语法与Latex公式汇总

1 基本语法 1.1 标题 语法如下&#xff1a; 效果如下&#xff1a; 1.2 字体样式 语法效果普通正文字体普通正文字体*倾斜字体*倾斜字体**加粗字体**加粗字体***倾斜加粗字体***倾斜字体~~划线字体~~倾斜字体 1.3 分割线 语法如下&#xff1a; 效果如下&#xff1a; …

【C++11及其特性】C++类型转换

C类型转换目录 一.C语言的强制类型转换二.static_cast1.父类子类之间指针或引用的转换2.基本数据类型的转换3.空指针转换其他类型指针4.其他类型指针转换为空指针5.static_cast特点6.完整代码 三.reinterpret_cast1.数值与指针之间的转换2.不同类型指针和引用之间的转换3.reint…

【网络安全】重置邮件逻辑漏洞导致账户接管

未经许可&#xff0c;不得转载。 文章目录 正文 正文 目标&#xff1a;xxx.com 点击重置密码&#xff0c;系统会发送一封链接至邮箱。响应如下&#xff1a; 从上图中可以看到&#xff0c;validationSession对象中有一个sessionID 而收到的链接中的token和sessionID的值是一样…

总结之Coze 是一站式 AI Bot 开发平台——使用coze(一)

Coze 是什么&#xff1f; Coze 是新一代一站式 AI Bot 开发平台。无论你是否有编程基础&#xff0c;都可以在 Coze 平台上快速搭建基于 AI 模型的各类问答 Bot&#xff0c;从解决简单的问答到处理复杂逻辑的对话。并且&#xff0c;你可以将搭建的 Bot 发布到各类社交平台和通讯…

[Leetcode 51][Hard]-n皇后问题-回溯

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 这种可以算是组合问题的变种&#xff0c;在回溯函数中我们要保存当前已放置皇后的所有位置&#xff0c;同时递归调用时要进行寻找下一个皇后的放置位置。那么我们可以逐行遍历棋盘并作为递归调…

STM32学习记录-10-2-SPI通信(硬件)

1 SPI外设简介 STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担 可配置8位/16位数据帧、高位先行/低位先行 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256) 支持多主机模型、主或从操作 可精简为半双工/单工通信 支持…

python源码 PBOCMaster MAC的计算函数及计算过程 2des

注意最后一步要用整个key加密 计算过程&#xff1a; MAC&#xff1a; PBOC-MAC DES算法 密钥 长度16(0x10)字节 57 75 20 4D 69 61 6F 6A 75 6E 40 47 26 44 43 11 初始向量 长度8(0x08)字节 00 00 00 00 00 00 00 00 数据 长度74(0x4A)字节 43 48 45 4E 48 41 4F 2D 50 43 7…

如何成为一个飞控算法工程师?

兄弟&#xff0c;这个问题问得好&#xff0c;但也别想着靠看几本书就能一步登天。飞控算法这玩意儿&#xff0c;真要干好了&#xff0c;不是简简单单几个公式几个库就能搞定的。你本科电子专业有点基础&#xff0c;玩过四轴飞行器也算是入门了&#xff0c;但要搞真算法&#xf…

做克隆虚拟机的basic

新建一台虚拟机&#xff08;之前写的有这一步&#xff09; 虚拟机里操作 vi /etc/hostname 改称basic (可改可不改) vi /etc/sysconfig/network-scripts/ifcfg-ens33 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic DEFROUTEyes IPV4_FAILURE_FATALno IP…

★ 算法OJ题 ★ 力扣 LCR179 - 和为 s 的两个数字

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;小诗歌剧将和大家一起做一道双指针算法题--和为 s 的两个数字~ 目录 一 题目 二 算法解析 三 编写算法 一 题目 LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09; 二 算法解析 …

静态工厂模式(简单工厂模式)与动态工厂模式(工厂方法模式)

1. 简单工厂模式 核心是定义一个创建对象的接口&#xff0c;将对象的创建和本身的业务逻辑分离&#xff0c;降低系统的耦合度&#xff0c;使得两个修改起来相对容易些&#xff0c;当以后实现改变时&#xff0c;只需要修改工厂类即可。该模式对对象创建管理方式最为简单&#x…

list的使用与迭代器的模拟实现

前面学习了string&#xff0c;vector类的使用及模拟&#xff0c;但是它们有一个统一的特点就是底层的内存是连续的&#xff0c;因此迭代器的实现也很简单。现在我们开始学习list类的使用&#xff0c;模拟实现&#xff0c;来看看这个底层内存不是连续的有什么特别之处&#xff0…

2024 SEKAI-CTF(nolibc speedpwn life_simulator_2)

文章目录 nolibcexp speedpwnexp life_simulator_2委托构造函数委托构造函数的语法解释 std:remove和std:erase代码解释原理内存管理注意事项 思路1. 背景2. 示例代码3. 解释 vector插入逻辑1. 函数参数2. 本地变量3. 逻辑分析4. 扩容逻辑5. 直接插入逻辑6. 返回结果 exp nolib…

集成电路学习:什么是FPGA现场可编程门阵列

一、FPGA&#xff1a;现场可编程门阵列 FPGA&#xff0c;全称Field Programmable Gate Array&#xff0c;即现场可编程门阵列&#xff0c;是一种超大规模可编程逻辑器件。它由可编程逻辑资源、可编程互连资源和可编程输入输出资源组成&#xff0c;主要用于实现以状态机为主要特…

【计算机组成原理】七、输入/输出系统:2.I/O接口、I/O控制方式

I/O接口、I/O控制方式 2. I/O接口 文章目录 I/O接口、I/O控制方式2. I/O接口2.1 I/O接口的作用2.2 结构2.3 工作原理2.4 I/O端口2.5 分类 3. I/O控制方式3.1程序查询方式3.2程序中断方式3.2.1中断系统3.2.2工作流程3.2.3多重中断与中断屏蔽技术3.2.4程序中断方式 3.3DMA控制方…

Excel技巧(二)

函数 SUMIFS函数 用于计算其满足多个条件的全部参数的总量 语法&#xff1a;SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) COUNTIFS函数 计算多个区域中满足给定条件的单元格的个数 语法&#xff1a;countifs(criteria_range1,crit…