当前位置: 首页 > news >正文

Linux驱动开发(三)--新字符设备驱动开发 LED驱动开发升级

1、新字符设备驱动原理

使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会
带来两个问题
  • 需要我们事先确定好哪些主设备号没有使用
  • 会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为200,那么 0~1048575(2^20-1) 这个区间的次设备号就全部都被 LED 一个设备分走了。这样太浪费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设

旧字符驱动模式,如下所示:

新字符驱动模型,如下所示:

2、分配和释放设备号

解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请,需要几个就申请
几个, Linux 内核分配设备可以使用的设备号
如果没有指定设备号的话就使用如下函数来申请设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可
int register_chrdev_region(dev_t from, unsigned count, const char *name)
注销字符设备之后要释放掉设备号 ,不管是通过 alloc_chrdev_region 函数还是
register_chrdev_region 函数申请的设备号,统一使用如下释放函数:
void unregister_chrdev_region(dev_t from, unsigned count)
综上所述,新字符设备驱动下,设备号分配示例代码如下:
 int major; /* 主设备号 */int minor; /* 次设备号 */dev_t devid; /* 设备号 */if (major) { /* 定义了主设备号 */devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0*/register_chrdev_region(devid, 1, "test");} else { /* 没有定义设备号 */alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */major = MAJOR(devid); /* 获取分配号的主设备号 */minor = MINOR(devid); /* 获取分配号的次设备号 */
}

注销设备很简单,因为我们的CNT是1嘛,如下所示:

unregister_chrdev_region(devid, 1);

3、新的字符设备注册方法(cdev)

其中我们要涉及用到cdev,在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体在 include/linux/cdev.h 文件中的定义如下:

 struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};
cdev 中有两个重要的成员变量: ops dev ,这两个就是字符设备文件操作函数集合
file_operations 以及设备号 dev_t 。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个
变量就表示一个字符设备,如下所示:
struct cdev test_cdev;
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。
使用 cdev_init 函数初始化 cdev 变量和向 Linux 系统添加字符设备 (cdev 结构体变量)的示例代码如下:
struct cdev testcdev;/* 设备操作函数 */static struct file_operations test_fops = {.owner = THIS_MODULE,/* 其他具体的初始项 */
};testcdev.owner = THIS_MODULE;cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */cdev_add(&testcdev, devid, 1); /* 添加字符设备 */

卸载删除驱动时如下所示:

cdev_del(&testcdev); /* 删除 cdev */

4、创建以及删除类和设备(class、device)

 struct class *class; /* 类 */ struct device *device; /* 设备 */dev_t devid; /* 设备号 */ /* 驱动入口函数 */static int __init led_init(void){/* 创建类 */class = class_create(THIS_MODULE, "xxx");/* 创建设备 */device = device_create(class, NULL, devid, NULL, "xxx");return 0;}/* 驱动出口函数 */static void __exit led_exit(void){/* 删除设备 */device_destroy(newchrled.class, newchrled.devid);/* 删除类 */class_destroy(newchrled.class);}module_init(led_init);module_exit(led_exit);

整体案例代码如下所示:

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define NEWCHRLED_CHT    1         /* 设备号个数 */
#define NEWCHRLED_NAME "newchrled" /* 设备名字   */
#define LEDOFF           0         /* 关灯         */
#define LEDON            1         /* 开灯         */#define CCM_CCGR1_BASE         (0X020C406C) 
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE          (0X0209C000)
#define GPIO1_GDIR_BASE        (0X0209C004)static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;struct newchrled_dev{dev_t devid;           /* 设备号 */struct cdev cdev;      /* cdev   */struct class *class;   /* 类      */struct device *device;  /* 设备     */int major;             /* 主设备号*/int minor;			   /* 次设备号 */
}struct newchrled_dev newchrled; /* led设备 *//** @description : LED 打开/关闭* @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED* @return : 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3); writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);} 
}static int led_open (struct inode *inodp, struct file *filp)
{filp->private_data = &newchrled; return 0;
}static ssize_t led_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf,buf,cnt);if(retvalue < 0){printk("kernel write failed\r\n");return -EFAULT;}ledstat = databuf[0];if(ledstat == LEDON){led_switch(LEDON);}else if(ledstat == LEDOFF){led_switch(LEDOFF);}return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int led_release (struct inode *inodp, struct file *filp)
{return 0;
}static struct file_operations newchrled_fops = {.owner   = THIS_MODULE,.open    = led_open,.read    = led_read,.write   = led_write,.release = led_release,
};static int __init led_init(void){int retvalue = 0;u32 val = 0;IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);GPIO1_DR = ioremap(GPIO1_DR_BASE,4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);/* 2、使能 GPIO1 时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); /* 清除以前的设置 */val |= (3 << 26); /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置 GPIO1_IO03 的复用功能,将其复用为* GPIO1_IO03,最后设置 IO 属性。*/writel(5, SW_MUX_GPIO1_IO03);/* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置 GPIO1_IO03 为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3); /* 清除以前的设置 */val |= (1 << 3); /* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭 LED */val = readl(GPIO1_DR);val |= (1 << 3); writel(val, GPIO1_DR);/*	//设备号 名字 字符模型驱动的一个结构体retvalue = register_chrdev(LED_MAJOR,LED_NAME,&newchrled_fops);if(retvalue < 0){// exit}	*//* 注册字符设备驱动 *//* 1、创建设备号 */if(newchrled.major){newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, NEWCHRLED_CHT, NEWCHRLED_NAME);} else {alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CHT, NEWCHRLED_NAME);newchrled.major = MAJOR(newchrled.devid);newchrled.minor = MINOR(newchrled.devid);}printk("newchrled major = %d,minor = %d \r\n",newchrled.major,newchrled.minor);/* 2、初始化     cdev */newchrled.cdev.owner = THIS_MODULE;cdev_init(&newchrled.cdev,&newchrled_fops);/* 3、添加一个 cdev */cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CHT);/* 4、创建类 */newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);if(IS_ERR(newchrled.class)){return PTR_ERR(newchrled.class);}/* 5、创建设备 */newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);if(IS_ERR(newchrled.device)){return PTR_ERR(newchrled.device);}return 0;
}static void __exit led_exit(void){iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* unregister_chrdev(LED_MAJOR,LED_NAME); *//* 注销字符设备 */cdev_del(&newchrled.cdev);unregister_chrdev_region(newchrled.devid, NEWCHRLED_CHT);device_destroy(newchrled.class,newchrled.devid);class_destroy(newchrled.class);
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("7yewh");

相关文章:

Linux驱动开发(三)--新字符设备驱动开发 LED驱动开发升级

1、新字符设备驱动原理 使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可&#xff0c;但是这样会 带来两个问题 需要我们事先确定好哪些主设备号没有使用 会将一个主设备号下的所有次设备号都使用掉&#xff0c;比如现在设置 LED 这个主设备号为200&…...

MCU的最佳存储方案CS创世 SD NAND

大家都知道MCU是一种"麻雀"虽小&#xff0c;却"五脏俱全"的主控。它的应用领域非常广泛&#xff0c;小到手机手表&#xff0c;大到航空航天的设备上都会用到MCU.市面上目前几个主流厂商有意法半导体&#xff08;其中最经典的一款就是STM32系列&#xff09;…...

40岁学习java是否需要报班学习?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「java的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“666”之后私信回复“666”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;应该不需要。各种公开免费的…...

Vitis Accelerated Libraries 学习笔记--OpenCV 运行测试

目录 1. 简介 2. 实例测试 2.1 实例介绍 2.2 创建工程 2.2.1 创建工程 2.2.2 获取路径 2.2.3 设置路径 2.2.4 打开工程 2.2.5 添加文件 2.2.6 启动 GUI 2.2.7 配置 csim 参数 3 常见错误 3.1 核心共享库报错 4. 总结 1. 简介 在《Vitis Accelerated Libraries …...

加固三防平板如何提高轨道交通系统的运营效率?

在当今快节奏的社会中&#xff0c;轨道交通系统作为城市交通的重要组成部分&#xff0c;其运营效率的提升对于缓解交通拥堵、满足人们的出行需求以及促进城市的发展具有至关重要的意义。而加固三防平板作为一种先进的技术设备&#xff0c;正逐渐在轨道交通领域发挥着关键作用&a…...

Django 靓号管理系统:实现登录功能

本文将详细介绍如何在 Django 靓号管理系统中实现登录功能,包括用户认证、验证码生成、以及中间件的使用。我们将逐步展示所有相关代码,并附带详细注释。 1. 项目结构 首先,让我们看一下项目的基本结构: number ├── manage.py ├── monaco.ttf ├── number │ …...

【Solr 学习笔记】Solr 源码启动教程

Solr 源码启动教程 本教程记录了如何通过 IDEA 启动并调试 Solr 源码&#xff0c;从 Solr9 开始 Solr 项目已由 ant 方式改成了 gradle 构建方式&#xff0c;本教程将以 Solr 9 为例进行演示&#xff0c;IDE 选择使用 IntelliJ IDEA。 Solr github 地址&#xff1a;https://gi…...

Java中的事件驱动编程模型

Java中的事件驱动编程模型 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我将为大家介绍Java中的事件驱动编程模型。事件驱动编程模型是一种以事件为核心驱…...

Python 语法基础一

1.变量 python 中变量很简单&#xff0c;不需要指定数据类型&#xff0c;直接使用等号定义就好。python变量里面存的是内存地址&#xff0c;也就是这个值存在内存里面的哪个地方&#xff0c;如果再把这个变量赋值给另一个变量&#xff0c;新的变量通过之前那个变量知道那个变量…...

从零开始:Spring Boot 中使用 Drools 规则引擎的完整指南

规则引擎作用 规则引擎主要用于将业务逻辑从应用程序代码中分离出来&#xff0c;提高系统的灵活性和可维护性。规则引擎通过预定义的规则来处理输入数据并做出相应的决策&#xff0c;从而实现业务逻辑的自动化和动态调整。 例如 门店信息校验&#xff1a;美团点评在门店信息…...

工业边缘计算网关

1 介绍 HINETG系列边缘计算网关&#xff08;Linux操作系统&#xff09;&#xff0c;是华辰智通的—款面向工业现场设备接入、数据采集、设备监控的工业级边缘计算网关。采用ARM Cortex-A7 800MHz高性能CPU,拥有以太网、串口、CAN口、IO口等丰富的接口&#xff0c;支持以太网、…...

【C++ 初阶路】--- 类和对象(末)

目录 一、const成员1.1 取地址及const取地址操作符重载 二、再谈构造函数2.1 构造函数体赋值2.2 初始化列表2.3 explicit关键字 三、static成员3.1 概念3.2 特性 四、友元4.1 友元函数4.2 友元类 五、内部类六、匿名对象 一、const成员 将const修饰的“成员函数”称之为const成…...

bable 【实用教程】

简介 bable 用于将 ES6 的语法编译为 ES5 只关心语法&#xff0c;不关心 API 是否正确。不处理模块化&#xff08;webpack 会处理&#xff09; 搭建开发环境 安装相关的包 npm i babel/cli babel/core babel/preset-env新建文件 .babelrc&#xff0c;内容为 { "presets…...

Android中使用startActivityForResult启动活动

Android中使用startActivityForResult启动活动 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在本文中&#xff0c;我们将深入探讨Android开发中使用startActi…...

NineData和华为云在一起!提供一站式智能数据库DevOps平台

以GuassDB数据库为底座 NineData和华为云一起 为企业提供 一站式智能数据库DevOps平台 帮助开发者 高效、安全地完成 数据库SQL审核 访问控制、敏感数据保护等 日常数据库相关开发任务 NineData 智能数据管理平台 NineData 作为新一代的云原生智能数据管理平台&#xf…...

深入解析 Redisson分布式锁看门狗机制

一、Redisson分布式锁概述 1.1 分布式锁的意义 在分布式系统中&#xff0c;多个节点可能同时访问共享资源&#xff0c;导致数据不一致或竞态条件。分布式锁通过协调不同节点对共享资源的访问&#xff0c;确保数据的一致性和并发访问的安全性。 1.2 Redisson分布式锁的优势 …...

Apache Arrow 和数据的未来:开放标准推动人工智能发展

Apache Arrow 是一种开源列式内存格式&#xff0c;适用于平面数据和分层数据。在现代数据湖中&#xff0c;开放数据格式&#xff08;如 Apache Arrow&#xff09;位于现代对象存储的存储层中。这些格式成为对象存储中的对象。 在最新版本中&#xff0c;Apache Arrow 宣布计划从…...

Vue项目生产环境的打包优化

Vue项目生产环境的打包优化 前言 在这篇文章我们讨论Vue项目生产环境的打包优化&#xff0c;并按步骤展示实际优化过程中的修改和前后对比。 背景 刚开始的打包体积为48.71M 优化 步骤一&#xff1a;删除viser-vue viser-vue底层依赖antv/g2等库一并被删除&#xff0c;…...

oracle数据库之使用Java程序调用存储过程(二十四)

在Oracle数据库中&#xff0c;你可以使用Java程序来调用存储过程。这通常涉及几个步骤&#xff1a;首先&#xff0c;确保你的Java环境能够连接到Oracle数据库&#xff1b;其次&#xff0c;使用JDBC&#xff08;Java Database Connectivity&#xff09;来调用存储过程。 以下是…...

西电953总分第一、专业课第一考研上岸

今年上岸西电杭研院网信院网络与信息安全专业&#xff0c;总分370分&#xff0c;专业课116分&#xff0c;分别是总分第一名&#xff0c;专业课第一名&#xff0c;感谢研梦的953叶学姐&#xff0c;非常负责&#xff0c;本硕大佬学姐&#xff0c;当年密码学38分选手&#xff08;满…...

pytorch-模型训练

目录 1. 模型训练的基本步骤1.1 train、test数据下载1.2 train、test数据加载1.3 Lenet5实例化、初始化loss函数、初始化优化器1.4 开始train和test 2. 完整代码 1. 模型训练的基本步骤 以cifar10和Lenet5为例 1.1 train、test数据下载 使用torchvision中的datasets可以方便…...

Linux /proc目录总结

1、概念 在Linux系统中&#xff0c;/proc目录是一个特殊的文件系统&#xff0c;通常被称为"proc文件系统"或"procfs"。这个文件系统以文件系统的方式为内核与进程之间的通信提供了一个接口。/proc目录中的文件大多数都提供了关于系统状态的信息&#xff0…...

【JavaEE】浅谈线程(二)

线程 线程的常见属性 线程属性可以通过下面的表格查看。 •ID 是线程的唯⼀标识&#xff0c;不同线程不会重复 • 名称是各种调试⼯具⽤到&#xff08;如jconsoloe&#xff09; • 状态表示线程当前所处的⼀个情况&#xff0c;下⾯我们会进⼀步说明 • 优先级高的线程理论上来…...

爬虫:爬取知乎热榜一级评论及回答2024不包含翻页

一、先上结果&#xff08;注:本文仅为兴趣爱好探究&#xff0c;请勿进行商业利用或非法研究&#xff0c;负责后果自负&#xff0c;与作者无关&#xff09; 1、爬标题及其具体内容 2、抓标题下的对应回答 3、爬取对应一级评论 二、上流程 1、获取cookies&#xff08;相信哥哥姐姐…...

AI 编程探索- iOS动态标签控件

需求分析&#xff1a; 标签根据文字长度&#xff0c;自适应标签居中显示扩展 超过内容显示范围&#xff0c;需要换行显示&#xff0c;且保持居中显示 AI实现过程 提问&#xff1a; 回答&#xff1a; import UIKit import SnapKitclass DynamicLabelsContainerView: UIView…...

计算机网络——数据链路层(数据链路层概述及基本问题)

链路、数据链路和帧的概念 数据链路层在物理层提供服务的基础上向网络层提供服务&#xff0c;其主要作用是加强物理层传输原始比特流的功能&#xff0c;将物理层提供的可能出错的物理连接改造为逻辑上无差错的数据链路&#xff0c;使之对网络层表现为一条无差错的链路。 链路(…...

【前端】前端权限管理的实现方式:基于Vue项目的详细指南

前端权限管理的实现方式&#xff1a;基于Vue项目的详细指南 在Web开发中&#xff0c;前端权限管理是一个确保应用安全性和优化用户体验的关键部分。本文将详细介绍前端权限管理的几种实现方式&#xff0c;并通过Vue项目中的代码示例来演示具体实现方法。 前端权限管理的基本实…...

MySQL数据库基础练习系列——教务管理系统

项目名称与项目简介 教务管理系统是一个旨在帮助学校或教育机构管理教务活动的软件系统。它涵盖了学生信息管理、教师信息管理、课程管理、成绩管理以及相关的报表生成等功能。通过该系统&#xff0c;学校可以更加高效地处理教务数据&#xff0c;提升教学质量和管理水平。 1.…...

windowns server2016服务器配置php调用powerpoint COM组件

解决问题&#xff1a;windowns server2016服务器配置php调用powerpoint COM组件 环境&#xff1a; windows server2016 宝塔&#xff08;nginxmysqlphp7.2&#xff09; IIS 搭建宝塔&#xff1a; 下载地址&#xff1a;https://www.bt.cn/download/windows.html ​ 安装使用&…...

Git之checkout/reset --hard/clean -f区别(四十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…...

IP怎么屏蔽网站域名/国家培训网官网

Vue中的缩写&#xff1a;v-bind、v-on v-bind 缩写&#xff1a;:预期&#xff1a;any (with argument) | Object (without argument)参数&#xff1a;attrOrProp (optional)修饰符&#xff1a; .prop - 被用于绑定 DOM 属性。.camel - (2.1.0) 将 kebab-case 特性名转换为 came…...

个人博客网站设计/百度搜索网站排名

python常见的错误有 1.NameError变量名错误 2.IndentationError代码缩进错误 3.AttributeError对象属性错误 详细讲解 1.NameError变量名错误 报错&#xff1a; >>> print aTraceback (most recent call last):File "", line 1, in NameError: name a is no…...

免费建设自己的网站/杭州新站整站seo

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼#include int JgYr(int yr){if (yr%40&&yr%100!0yr%1000&&yr%4000)return 1;elsereturn 0;}int CalWkd(int yr,int mth){int ds0,i,rst,wkd;int mthd[13]{0,31,28,31,30,31,30,31,31,30,31,30,31};if (JgYr(yr))mt…...

简单网站系统/百度推广账户登录

PHP 字符串变量一个字符串(string)就是由一系列的字符组成&#xff0c;其中每个字符等同于一个字节。字符串变量用于存储并处理文本。PHP 中的字符串变量字符串变量用于包含有字符的值。在创建字符串之后&#xff0c;我们就可以对它进行操作了。您可以直接在函数中使用字符串&a…...

标准化建设发展委员会官方网站/网站如何推广营销

redis做压测可以用自带的redis-benchmark工具&#xff0c;使用简单 压测需要一段时间&#xff0c;因为它需要依次压测多个命令的结果&#xff0c;如&#xff1a;get、set、incr、lpush等等&#xff0c;所以我们需要耐心等待&#xff0c;如果只需要压测某个命令&#xff0c;如&a…...

wordpress app 开发教程/网站页面设计模板

互联网时代&#xff0c;瞬息万变。一个小小的走错&#xff0c;就有可能落后于别人。我们没办法去预测任何行业、任何职业未来十年会怎么样&#xff0c;因为未来谁都不能确定。只能说只要有互联网存在&#xff0c;程序员依然是个高薪热门行业。只要跟随着时代的脚步&#xff0c;…...