MX6ULL学习笔记(九)MISC设备驱动

news2024/12/23 10:28:41

前言

        misc 的意思是混合、杂项的,因此 MISC 驱动也叫做杂项驱动。也就是当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱动,通常嵌套在 platform 总线驱动中,实现复杂的驱动,接下来就来讲下一下MISC的使用,其实总结就是一句话,用MISC 设备驱动来简化字符设备驱动的编写,也就是替代我们之前注册字符设备的那一堆操作


一.MISC设备驱动简介

        所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。随着 Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号, MISC 设备驱动就用于解决此问题。

         MISC 设备会自动创建 cdev,不需要像我们以前那样手动创建,因此采用 MISC 设备驱动可以简化字符设备驱动的编写

1.miscdevice 设备结构体:

同样的,我们要使用MISC,就需要向 Linux 注册一个 miscdevice 设备, miscdevice是一个结构体,定义在文件 include/linux/miscdevice.h 中,内容如下:

struct miscdevice  {
    int minor;
    const char *name;
    const struct file_operations *fops;
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
};

    这个结构体需要我们填入的参数有:minor、 name 和 fops 这三个成员变量。

<1>minor:

        minor 表示子设备号, MISC 设备的主设备号为 10,这个是固定的,需要用户指定子设备号, Linux 系统已经预定义了一些 MISC 设备的子设备号,这些预定义的子设备号定义在include/linux/miscdevice.h 文件中,如下所示:

#define PSMOUSE_MINOR       1
#define MS_BUSMOUSE_MINOR   2   /* unused */
#define ATIXL_BUSMOUSE_MINOR    3   /* unused */
/*#define AMIGAMOUSE_MINOR  4   FIXME OBSOLETE */
#define ATARIMOUSE_MINOR    5   /* unused */
#define SUN_MOUSE_MINOR     6   /* unused */
#define APOLLO_MOUSE_MINOR  7   /* unused */
#define PC110PAD_MINOR      9   /* unused */
......
#define VHOST_VSOCK_MINOR   241
#define RFKILL_MINOR        242
#define MISC_DYNAMIC_MINOR  255

我们在使用的时候可以从这些预定义的子设备号中挑选一个,当然也可以自己定义,只要这个子设备号没有被其他设备使用接口。

<2>name :

        name 就是此 MISC 设备名字,当此设备注册成功以后就会在/dev 目录下生成一个名为 name的设备文件。

<3>fops :

fops 是字符设备的操作函数集合, MISC 设备驱动最终是需要使用用户提供的 fops操作集合。也就是之前我们自己手动注册字符设备的操作函数:

/* 设备操作函数结构体 */
static const struct file_operations gpioled_fops = {
	.owner		= THIS_MODULE,
	.open		= gpioled_open,
	.read		= gpioled_read,
    .write      = gpioled_write,
    .release    = gpioled_release
};

2.miscdevice 注册函数:

函数原型如下:

int misc_register(struct miscdevice * misc)
 
函数参数和返回值含义如下:
misc:要注册的 MISC 设备。
返回值: 负数,失败; 0,成功。

以前我们需要自己调用一堆的函数去创建设备,比如在以前的字符设备驱动中我们会使用如下几个函数完成设备创建过程:

 /* 传统的创建设备过程 */
alloc_chrdev_region();    /* 申请设备号 */
cdev_init();              /* 初始化 cdev */
cdev_add();               /* 添加 cdev */
class_create();           /* 创建类 */
device_create();          /* 创建设备 */

 现在我们可以直接使用 misc_register 一个函数来完成上面的传统的创建设备过程中的这些步骤。如下所示:

   ret = misc_register(&led_miscdev);
    if(ret < 0){
		printk("misc device register failed!\r\n");
		return -EFAULT;
	}

3.misc_deregister卸载函数:

当我们卸载设备驱动模块的时候需要调用 misc_deregister 函数来注销掉 MISC 设备,函数原型如下:

int misc_deregister(struct miscdevice *misc)
 
函数参数和返回值含义如下:
misc:要注销的 MISC 设备。
返回值: 负数,失败; 0,成功。

以前注销设备驱动的时候,我们需要调用一堆的函数去删除此前创建的 cdev、设备等等内容,如下所示:

/* 传统的删除设备的过程 */
cdev_del();                 /* 删除 cdev */
unregister_chrdev_region(); /* 注销设备号 */
device_destroy();           /* 删除设备 */
class_destroy();            /* 删除类 */

 现在我们只需要一个 misc_deregister 函数即可完成传统的删除设备的过程中的这些工作。关于MISC 设备驱动就讲解到这里,接下来我们就使用 platform 加 MISC 驱动框架来编写 led驱动。如下所示:

misc_deregister(&led_miscdev);

二、设备树的修改:

在设备树下的根节点添加以下结点:

	gpioled{
		#address-cells = <1>;
		#size-cells    = <1>;
		compatible = "led-gpio";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led>;
		gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
		state = "okay";

	};
	

在iomux节点下添加;

		pinctrl_led:ledgrp{
			fsl,pin=<
			 /* 配置 GPIO1_IO03 的 IO 属性 
			 *bit 16:0 HYS 关闭 
			 *bit [15:14]: 00 默认下拉 
			 *bit [13]: 0 kepper 功能 
			 *bit [12]: 1 pull/keeper 使能 
			 *bit [11]: 0 关闭开路输出 
			 *bit [7:6]: 10 速度 100Mhz 
			 *bit [5:3]: 110 R0/6 驱动能力 
			 *bit [0]: 0 低转换率 
			 */
			MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
			>;

三、驱动代码的编写

1.引入框架

        框架就使用我们之前的platfrom-led的框架不过在peobe函数里面,我们要把关于那些字符设备注册初始化的部分删除了:

static int led_probe(struct platform_device *dev)
{
    myled_init(&gpioled);
    return 0;
}

 剩下gpio初始化部分就可以了。

2.创建miscdevice结构体

#define DEVICE_NAME         "miscled"
#define DEVICE_MINOR		145			/* 子设备号 */

/* MISC设备结构体 */
static struct miscdevice led_miscdev = {
	.minor = DEVICE_MINOR,
	.name = DEVICE_NAME,
	.fops = &gpioled_fops,
};

3.使用.miscdevice 注册函数

/*当谁列表的设备和驱动匹配上后执行的peobe函数*/
static int led_probe(struct platform_device *dev)
{
    int ret = 0;
    myled_init(&gpioled);

    ret = misc_register(&led_miscdev);
    if(ret < 0){
		printk("misc device register failed!\r\n");
		return -EFAULT;
	}
    return 0;
}

4.misc_deregister卸载函数

static int led_remove(struct platform_device *dev)
{
    gpio_set_value(gpioled.led_gpio,1);
    gpio_free(gpioled.led_gpio);
    misc_deregister(&led_miscdev);
	printk("gpioled exit!\r\n");
    return 0;
}

完整代码:

/**************头文件区域*********************************************************/
#include <linux/ide.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>

#include <linux/of.h> 
#include <linux/of_address.h> 
#include <linux/of_gpio.h> 
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>
#include <linux/miscdevice.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <linux/io.h>
/**********************************************************************************/
 
/************************函数定义-begin***********************************************/
static int gpioled_release(struct inode *inode, struct file *file);
static ssize_t gpioled_read(struct file *file, char __user *buf, size_t size, loff_t *ptr);
static ssize_t gpioled_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr);
static int gpioled_open(struct inode *inode , struct file *file);
static int led_probe(struct platform_device *dev);
static int led_remove(struct platform_device *dev);
/************************函数定义-end********************************************/


/************************宏定义-begin***********************************************/
#define DEVICE_NAME         "miscled"
#define DEVICE_MINOR		145			/* 子设备号 */
#define DEVICE_CNT          1
#define BEEP_ON              1
#define BEEP_OFF             0
/************************宏定义-end********************************************/

/************************结构体定义-begin***********************************************/
/* dtsled设备信息结构体 */
struct dts_dev
{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node *nd; /* 设备节点 */
    int led_gpio; /* led 所使用的 GPIO 编号 */
};
struct dts_dev gpioled; /* led设备 */

/* 设备操作函数结构体 */
static const struct file_operations gpioled_fops = {
	.owner		= THIS_MODULE,
	.open		= gpioled_open,
	.read		= gpioled_read,
    .write      = gpioled_write,
    .release    = gpioled_release
};

/* MISC设备结构体 */
static struct miscdevice led_miscdev = {
	.minor = DEVICE_MINOR,
	.name = DEVICE_NAME,
	.fops = &gpioled_fops,
};

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
	{ .compatible = "led-gpio" },
	{ /* Sentinel */ }
};

static struct platform_driver led_driver = {
    .driver = {
        .name = "imx6ul-led",
        .of_match_table = led_of_match,
    },
    .probe  =  led_probe,
    .remove =  led_remove,
};
/************************结构体定义-end***********************************************/


/************************file_operations操作函数-begin***********************************************/
static int gpioled_release(struct inode *inode, struct file *file)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
static ssize_t gpioled_read(struct file *file, char __user *buf, size_t size, loff_t *ptr)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
static ssize_t gpioled_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr)
{
    int ret;
    unsigned char databuf[1];
    unsigned char ledstate;
    struct dts_dev *dev = file->private_data;
    
    ret = __copy_from_user(databuf,buf,size);

    if(ret < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }
    ledstate = databuf[0];

    if(ledstate == BEEP_OFF){   
        gpio_set_value(dev->led_gpio,1);
    }
    else if(ledstate == BEEP_ON){
        gpio_set_value(dev->led_gpio,0);
    }
    
	return 0;
}
static int gpioled_open(struct inode *inode , struct file *file)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    file->private_data = &gpioled; /* 设置私有数据 */ 
	return 0;
}
/************************file_operations操作函数-end***********************************************/

/*****************led初始化函数************************/
static int myled_init(struct dts_dev *dev)
{
    int ret = 0;
   /* 1、设置 led 所使用的 GPIO */ 
    dev->nd = of_find_node_by_path("/gpioled");
    if(dev->nd == NULL){
        printk("gpioled node cant not found!\r\n");
        return -EINVAL;
    }
    else{
        printk("gpioled node hase been found!\r\n");
    }
    
    /* 2、 获取设备树中的 gpio 属性,得到 led 所使用的 led 编号 */ 
    dev->led_gpio =  of_get_named_gpio(dev->nd,"gpios",0);
    if(dev->led_gpio < 0)
    {
		printk("can't get led-gpio\r\n");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", dev->led_gpio); 
    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 led 灯 */ 
    ret = gpio_request(dev->led_gpio,"led");
    if(ret < 0){
        printk("led-gpio request fail\r\n"); 
        return -EINVAL;
    }
    ret = gpio_direction_output(dev->led_gpio,1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}
    return ret;
}

/************************platfrom操作函数-begin***********************************************/
/*当谁列表的设备和驱动匹配上后执行的peobe函数*/
static int led_probe(struct platform_device *dev)
{
    int ret = 0;
    myled_init(&gpioled);

    ret = misc_register(&led_miscdev);
    if(ret < 0){
		printk("misc device register failed!\r\n");
		return -EFAULT;
	}
    return 0;
}
static int led_remove(struct platform_device *dev)
{
    gpio_set_value(gpioled.led_gpio,1);
    gpio_free(gpioled.led_gpio);
    misc_deregister(&led_miscdev);
	printk("gpioled exit!\r\n");
    return 0;
}
/************************platfrom操作函数-endn***********************************************/



static int __init gpioled_init(void)
{
    return platform_driver_register(&led_driver);
}

static void __exit gpioled_exit(void)
{
    platform_driver_unregister(&led_driver);
}


module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("oudafa");



四、编写测试 APP

这里的测试APP和之前得没什么区别,不用改:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开led驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */

	/* 向/dev/led文件写入数据 */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

五、运行测试

1.编写makefile

并且使用make命令得到.ko文件和APP文件:

KERN_DIR = /home/odf/linux-imx/linux-imx

all:
	clear
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o miscledApp miscledApp.c 

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


obj-m += miscled.o 

2.加载模块

将编译出来 .ko文件 和 app文件 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中目录中,重启开发板,进入到目录 rootfs/lib/modules/4.1.15 目录中中,输入如下命令加载 驱动模块:

insmod miscled.ko 

所有的 misc 设备都属于同一个类, /sys/class/misc 目录下就是 misc 这个类的所有设备,每个设备对应一个子目录。

        驱动与设备匹配成功以后就会生成/dev/miscled这个设备驱动文件,输入如下命令查看这个文件的主次设备号:

ls -l /sys/class/misc 

结果如下所示: 

从上面可以看出, /dev/misc_beep 这个设备的主设备号为 10,次设备号为 144,和我们驱动程序里面设置的一致。

3.测试代码

输入如下命令打开 led:

./misc_beep_app /dev/miscled  1

输入如下命令关闭 led:

./misc_beep_app /dev/miscled  0

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

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

相关文章

.NET医院检验系统LIS源码,使用了oracle数据库,保证数据的隔离和安全性

医院检验系统LIS源码&#xff0c;LIS系统全套商业源码 LIS系统实现了实验室人力资源管理、标本管理、日常事务管理、网络管理、检验数据管理&#xff08;采集、传输、处理、输出、发布&#xff09;、报表管理过程的自动化&#xff0c;使实验室的操作人员和管理者从繁杂的手工劳…

开利网络的数字化技术加持下,加快扶贫和乡村振兴的效果和进程!

今日&#xff0c;来自山区省份的从事公益、区域民族文化传播、帮扶贫困地区脱贫、农业兴村贵州项目组一行来开利进行数字化脱贫、帮助乡村振兴解决方案探讨交流&#xff0c;交流中&#xff0c;开利网络总结出历经多年实践验证且行之有效的数字化经营、数字化建设经验得到与会成…

Elasticsearch:使用 OpenAI 生成嵌入并进行向量搜索 - nodejs

在我之前的文章&#xff1a; Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;一&#xff09;&#xff08;二&#xff09;&#xff08;三&#xff09;&#xff08;四&#xff09;​​​​​ 我详细地描述了如何使用…

做题总结 19. 删除链表的倒数第 N 个结点(快慢指针思想)

19. 删除链表的倒数第 N 个结点 最初的想法进阶实现&#xff08;Java&#xff09; 最初的想法 计算出链表中的节点总数&#xff0c;然后遍历找到目标节点并删除。 class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode cur head;int count0;w…

直流电、交流电、电磁波、光之间的联系

直流电、恒定磁场、交流电、交变磁场、电磁波、光之间的联系 频率为0Hz的直流电及恒定磁场 从频率的角度上看&#xff0c;直流电与恒定磁场的方向不变&#xff0c;频率为0Hz. 如可充电锂离子电池的电压3.7V, 干电池的电压1.5V. 磁铁的磁场方向从N极到S极&#xff0c;始终保持…

9mysql存储过程,存储函数和触发器

MySql 存储过程和触发器_mysql存储过程和触发器-CSDN博客文章浏览阅读162次。MySql存储过程、存储函数和触发器_mysql存储过程和触发器https://blog.csdn.net/chenniorange/article/details/132376605存储过程例子 -- A. 定义局部变量, 记录累加之后的值; -- B. 每循环一次, 就…

Ai 算法之Transformer 模型的实现: 一 、Input Embedding模块和Positional Embedding模块的实现

一 文章生成模型简介 比较常见的文章生成模型有以下几种&#xff1a; RNN&#xff1a;循环神经网络。可以处理长度变化的序列数据&#xff0c;比如自然语言文本。RNN通过隐藏层中的循环结构来传递时间序列中的信息&#xff0c;从而使当前的计算可以参照之前的信息。但这种模型…

一个算法一个例题教会你算法---0-1背包问题

动态规划 0-1背包问题 0-1背包问题就是求在有重量限制的情况下如何装入价值最大的物品。 啥也别说&#xff0c;直接看题&#xff1a; 现在有四个可以放的物品&#xff0c;w代表重量&#xff0c;v代表价值。 step1&#xff1a; 我们列一个背包重量 j 从0到5的表格&#xff0…

【STM32】STM32学习笔记-LED闪烁 LED流水灯 蜂鸣器(06-2)

00. 目录 文章目录 00. 目录01. GPIO之LED电路图02. GPIO之LED接线图03. LED闪烁程序示例04. LED闪烁程序下载05. LED流水灯接线图06. LED流水灯程序示例07. 蜂鸣器接线图08. 蜂鸣器程序示例09. 下载10. 附录 01. GPIO之LED电路图 电路图示例1 电路图示例2 02. GPIO之LED接线图…

Navicat 技术指引 | 适用于 GaussDB 分布式的数据查看器

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

CNN发展史脉络 概述图整理

CNN发展史脉络概述图整理&#xff0c;学习心得&#xff0c;供参考&#xff0c;错误请批评指正。 相关论文&#xff1a; LeNet&#xff1a;Handwritten Digit Recognition with a Back-Propagation Network&#xff1b; Gradient-Based Learning Applied to Document Recogniti…

leaflet使用热力图报L找不到的问题ReferenceError: L is not defined at leaflet-heat.js:11:3

1.在main.js中直接引入会显示找不到L 2.解决办法 直接在组件中单独引入使用 可以直接显示出来。 至于为什么main中不能引入为全局&#xff0c;我是没找到&#xff0c;我的另外一个项目可以&#xff0c;新项目不行&#xff0c;不知哪里设置的问题

在linux服上使用nginx+tomcat部署若依前后端分离版本(RuoYi-Vue)

一、先拉工程&#xff0c;地址&#xff1a;RuoYi-Vue: &#x1f389; 基于SpringBoot&#xff0c;Spring Security&#xff0c;JWT&#xff0c;Vue & Element 的前后端分离权限管理系统&#xff0c;同时提供了 Vue3 的版本 二、在window上用idea打开跑通&#xff0c;可参考…

GPT-4V 在保险行业的应用

在科技的进步中&#xff0c;人工智能与大数据技术的结合产生了巨大的能量&#xff0c;推动了各行各业的创新与变革。OpenAI&#xff0c;作为全球领先的人工智能研发机构&#xff0c;在今年的9月25日&#xff0c;以一种崭新的方式&#xff0c;升级了其旗下的GPT-4模型。这次的升…

解决msvcr120.dll文件丢失问题

项目场景&#xff1a; 在VMware虚拟机中安装win7家庭版系统&#xff0c;安装MySQL数据库&#xff0c;部署项目文件。 问题描述 安装MySQL数据库过程中提示“msvcr120.dll文件丢失”。 原因分析&#xff1a; 提示丢失msvcr120.dll文件&#xff0c;我们首先要到C:\Windows\Sys…

SD-WAN解决外贸企业网络问题

为了获取全球客户&#xff0c;占领更多的市场&#xff0c;越来越多的外贸企业出现。外贸企业在发展业务的过程中会遇到很多困难&#xff0c;海外网络访问问题就是其中之一。目前该问题主要有三种解决方案&#xff1a;VPN、MPLS专线以及SD-WAN专线。 VPN通过在公网上面建立专用网…

3、ollvm移植

github: https://github.com/obfuscator-llvm/obfuscator/tree/llvm-4.0 先复制 include Obfuscation: /home/nowind/llvm/ollvm/obfuscator/include/llvm/Transforms/Obfuscation /home/nowind/llvm/llvm-project-9.0.1/llvm/include/llvm/Transforms/Obfuscation lib Ob…

【算法Hot100系列】两数相加

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

深度学习|词嵌入的演变

文本嵌入&#xff0c;也称为词嵌入&#xff0c;是文本数据的高维、密集向量表示&#xff0c;可以测量不同文本之间的语义和句法相似性。它们通常是通过在大量文本数据上训练 Word2Vec、GloVe 或 BERT 等机器学习模型来创建的。这些模型能够捕获单词和短语之间的复杂关系&#x…

利用poi实现将数据库表字段信息导出到word中

研发文档对于开发人员来说都不陌生了&#xff0c;而研发文档里重要的一部分就是表结构设计&#xff0c;需要我们在word建个表格把我们数据库中的表字段信息填进去&#xff0c;表多的话靠我们手动去填非常累人&#xff01;&#xff01;&#xff01; 因此作为开发人员可不可以写…