Linux驱动开发—Linux内核定时器概念和使用详解,实现基于定时器的字符驱动
文章目录
- 内核定时器概念
- 在Linux驱动模块中使用定时器
- 软定时器(Soft Timers)
- jiffies 含义
- 高精度定时器(High Resolution Timers)
- 实现倒计时字符设备驱动
内核定时器概念
在 Linux 内核中,定时器是用来管理和调度延迟操作的机制。Linux 内核提供了几种不同类型的定时器,每种定时器都有不同的特点和用途。以下是一些主要的定时器类型和相关概念:
-
POSIX 定时器:
- 这是一个基于 POSIX 标准的定时器,提供了一种用户空间的定时器接口。通过
timer_create()
、timer_settime()
和timer_gettime()
等系统调用,用户空间程序可以创建和管理定时器。
- 这是一个基于 POSIX 标准的定时器,提供了一种用户空间的定时器接口。通过
-
高精度定时器 (High Resolution Timers):
- 高精度定时器可以提供比普通定时器更高的时间精度。它们使用
hrtimer
API 来实现,适用于需要非常精确定时的场景,比如实时系统。
- 高精度定时器可以提供比普通定时器更高的时间精度。它们使用
-
软定时器 (Soft Timers):
- 软定时器是内核中的定时器,用于调度执行内核中的延迟操作。它们主要通过
mod_timer()
和del_timer()
等 API 进行管理。软定时器通常用于处理网络数据包或其他延迟任务。
- 软定时器是内核中的定时器,用于调度执行内核中的延迟操作。它们主要通过
-
延迟队列 (Delay Queues):
- 延迟队列是一种数据结构,用于在内核中排队等待延迟处理的任务。通过
init_timer()
和add_timer()
等函数,可以将任务添加到延迟队列中,等待指定时间后执行。
- 延迟队列是一种数据结构,用于在内核中排队等待延迟处理的任务。通过
-
定时器回调:
- 当定时器到期时,会调用相应的回调函数来执行预定的操作。回调函数通常会在内核态执行,进行一些需要延迟处理的任务。
定时器在内核中的实现涉及到多种机制,包括定时器队列、定时器中断以及内核调度机制等。具体使用和实现细节可以参考 Linux 内核文档和源代码。
在Linux驱动模块中使用定时器
在 Linux 驱动模块中使用定时器,通常有两种主要方式:使用软定时器和高精度定时器。
软定时器(Soft Timers)
软定时器在内核驱动中使用时,通常涉及到 timer_list
结构和相关 API。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/init.h>static struct timer_list my_timer;void timer_callback(struct timer_list *t)
{printk(KERN_INFO "Timer callback function called.\n");
}static int __init my_module_init(void)
{printk(KERN_INFO "Module initialized.\n");printk(KERN_INFO "System HZ value: %d\n", HZ);// 初始化定时器timer_setup(&my_timer, timer_callback, 0);// 设置定时器超时值(例如 500ms)mod_timer(&my_timer, jiffies + msecs_to_jiffies(500));return 0;
}static void __exit my_module_exit(void)
{// 删除定时器del_timer(&my_timer);printk(KERN_INFO "Module exited.\n");
}module_init(my_module_init);
module_exit(my_module_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marxist");
MODULE_DESCRIPTION("A simple Linux driver with a timer.");
关键点说明:
timer_setup
函数用于初始化timer_list
结构体并设置回调函数。mod_timer
函数用于设置定时器的超时时间。del_timer
函数用于删除定时器,防止模块卸载时定时器仍在执行。
效果如图:
其中 printk(KERN_INFO "System HZ value: %d\n", HZ);
输出当前系统的时钟中断频率,与jiffies 相关
jiffies 含义
在 Linux 内核中,jiffies
是一个用于表示系统启动以来的时间的全局计数器,它以“滴答”(tick)为单位。jiffies
是内核中时间管理的一个基本概念,广泛用于定时器和延迟操作。
定义:jiffies
是一个计数器,表示自系统启动以来经过的“时钟滴答”数。每个滴答的时间长度由系统的时钟中断频率决定,通常是 1 毫秒(即 1000 Hz)或 10 毫秒(即 100 Hz),但具体取决于内核配置。
单位:jiffies
的单位是系统时钟滴答。系统时钟滴答的频率(即每秒钟中断的次数)由内核配置中的 HZ
参数决定。例如,如果 HZ
设置为 1000,那么每秒会有 1000 个滴答,每个滴答大约是 1 毫秒。
计算时间:使用 jiffies
可以计算时间间隔。例如,如果你要设置一个定时器在 500 毫秒后触发,可以使用以下代码:
mod_timer(&my_timer, jiffies + msecs_to_jiffies(500));
这里,msecs_to_jiffies
函数将 500 毫秒转换为相应的 jiffies
数量。
溢出:由于 jiffies
是一个 unsigned long
类型的变量,它可能会在长时间运行后溢出。在现代 Linux 内核中,jiffies
的溢出问题一般不会对大多数应用造成问题,因为内核设计考虑了这个问题并进行了处理。
转换函数:
jiffies_to_msecs(jiffies)
:将jiffies
转换为毫秒。jiffies_to_timespec(jiffies)
:将jiffies
转换为timespec
结构。msecs_to_jiffies(milliseconds)
:将毫秒转换为jiffies
。
高精度定时器(High Resolution Timers)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/init.h>static struct hrtimer my_hrtimer;
static enum hrtimer_restart hrtimer_callback(struct hrtimer *timer)
{printk(KERN_INFO "High resolution timer callback function called.\n");return HRTIMER_NORESTART; // 不重复定时
}static int __init my_module_init(void)
{printk(KERN_INFO "Module initialized.\n");// 初始化高精度定时器hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);my_hrtimer.function = hrtimer_callback;// 设置定时器超时值(例如 500ms)ktime_t ktime = ktime_set(0, 500 * 1000000); // 500mshrtimer_start(&my_hrtimer, ktime, HRTIMER_MODE_REL);return 0;
}static void __exit my_module_exit(void)
{// 删除高精度定时器int ret = hrtimer_cancel(&my_hrtimer);if (ret)printk(KERN_INFO "The high resolution timer was still active.\n");printk(KERN_INFO "Module exited.\n");
}module_init(my_module_init);
module_exit(my_module_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marxist");
MODULE_DESCRIPTION("A simple Linux driver with a high resolution timer.");
关键点说明:
hrtimer_init
函数用于初始化高精度定时器。hrtimer_start
函数用于设置高精度定时器的超时时间。ktime_set
用于创建高精度时间值。hrtimer_cancel
函数用于取消高精度定时器。
效果如下:
实现倒计时字符设备驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/atomic.h>
#define DEVICE_NAME "countdown_device"static atomic_t counter = ATOMIC_INIT(0);static int major_number;
static struct class *my_class = NULL;
static struct device *my_device = NULL;
static struct cdev mydev; // 声明 cdev 结构体
static struct timer_list my_timer; // 声明定时器
static struct semaphore my_semaphore; // 定义信号量
static int count_val = 30; // 倒计时初始值// 定时器回调函数
void timer_callback(struct timer_list *t)
{count_val= atomic_read(&counter);// 每秒 -1if (count_val > 0){atomic_dec(&counter);printk("mytimer : %d",atomic_read(&counter));// 重新设置定时器以在 1 秒后触发mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));}else count_val = -1; // 代表结束
}static int device_open(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device opened\n");// 初始化定时器 ,并开启定时任务timer_setup(&my_timer, timer_callback, 0);mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));return 0;
}static int device_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "Device closed\n");// 删除定时器del_timer_sync(&my_timer);return 0;
}static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset)
{int count_val_copy;count_val_copy = atomic_read(&counter);printk("begin read new val: %d", count_val_copy);// 将数据从内核空间拷贝到用户空间if (copy_to_user(buffer, &count_val_copy, sizeof(count_val_copy))){printk("copy_to_user error");return -EFAULT;}return sizeof(count_val_copy);
}static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset)
{char buf[64];int new_value;// 获取信号量if (down_interruptible(&my_semaphore)){return -ERESTARTSYS;}// 从用户空间复制数据if (copy_from_user(buf, buffer, len)){up(&my_semaphore);return -EFAULT;}buf[len] = '\0';if (sscanf(buf, "%d", &new_value) == 1){count_val = new_value; // 从用户空间获取到倒计时的值}printk("write new val is %d",count_val);// 释放信号量up(&my_semaphore);//重新给原子变量赋值atomic_set(&counter, count_val); return len;
}static struct file_operations fops = {.open = device_open,.release = device_release,.read = device_read,.write = device_write,
};static int __init test_init(void)
{int retval;dev_t dev;printk(KERN_INFO "module init success\n");// 初始化信号量sema_init(&my_semaphore, 1);// 1. 动态分配主次设备号retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);if (retval < 0){printk(KERN_ERR "Failed to allocate major number\n");goto fail_alloc_chrdev_region;}major_number = MAJOR(dev);printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));// 2. 初始化 cdev 结构体并添加到内核cdev_init(&mydev, &fops);retval = cdev_add(&mydev, dev, 1);if (retval < 0){printk(KERN_ERR "Failed to add cdev\n");goto fail_cdev_add;}// 3. 创建设备类my_class = class_create(THIS_MODULE, "my_class");if (IS_ERR(my_class)){printk(KERN_ERR "Failed to create class\n");retval = PTR_ERR(my_class);goto fail_class_create;}// 4. 申请设备,内核空间就会通知用户空间的 udev 进行创建设备,驱动程序本身自己是创建不了文件的!my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);if (IS_ERR(my_device)){printk(KERN_ERR "Failed to create device\n");retval = PTR_ERR(my_device);goto fail_device_create;}printk(KERN_INFO "my_char_device: module loaded\n");return 0;fail_device_create:class_destroy(my_class);
fail_class_create:cdev_del(&mydev);
fail_cdev_add:unregister_chrdev_region(dev, 1);
fail_alloc_chrdev_region:return retval;
}static void __exit test_exit(void)
{dev_t dev = MKDEV(major_number, 0);if (my_device)device_destroy(my_class, dev);if (my_class)class_destroy(my_class);cdev_del(&mydev);unregister_chrdev_region(dev, 1);printk(KERN_INFO "my_char_device: module unloaded\n");
}module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");
用户端
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>#define DEVICE_PATH "/dev/countdown_device"int main()
{int fd;char buffer[64];int count_val = 60; // 倒计时初始值ssize_t bytes_written, bytes_read;int res_val;// 打开设备文件fd = open(DEVICE_PATH, O_RDWR);if (fd < 0){perror("Failed to open the device");return errno;}// 写入倒计时初始值snprintf(buffer, sizeof(buffer), "%d", count_val);bytes_written = write(fd, buffer, strlen(buffer));if (bytes_written < 0){perror("Failed to write to the device");close(fd);return errno;}printf("Written %d to the device\n", count_val);// 进行倒计时读取输出while (1){// 读取当前倒计时值bytes_read = read(fd, &res_val, sizeof(res_val));if (bytes_read < 0){perror("Failed to read from the device");close(fd);return errno;}// 打印当前倒计时值printf("Current countdown value: %d\n", res_val);// 休眠1秒sleep(1);// 判断倒计时是否结束if (res_val <= 0){printf("Countdown finished.\n");break;}}// 关闭设备文件close(fd);return 0;
}
效果如图:在内核中,使用定时器每秒对原子变量的值减-1
相关文章:
Linux驱动开发—Linux内核定时器概念和使用详解,实现基于定时器的字符驱动
文章目录 内核定时器概念在Linux驱动模块中使用定时器软定时器(Soft Timers)jiffies 含义高精度定时器(High Resolution Timers) 实现倒计时字符设备驱动 内核定时器概念 在 Linux 内核中,定时器是用来管理和调度延迟…...
mysql数据库:数据库,表和列的基本概念
mysql:数据库,表和列的基本概念以及导入和导出文件 数据库的概念和用途 数据库是一个有组织的数据集合,它们被存储在计算机上以便于管理和访问。数据库的主要目的是为了存储和管理数据,同时使数据能够被高效地访问、检索和更新。数…...
Nextjs 使用 graphql,并且接入多个节点
写在前面 随着区块链技术的流行,也促进了 subgraph 工具的兴起。那么如何在前端接入 graphql 节点就成了关键,其接入方式既存在与 restful 接口相类似的方式,也有其独特接入风格。本文将介绍如何接入 graphql 以及如何应对多个 graphql 节点…...
小结——知识注入
所谓知识注入,其实不该脱离于LLM的基础工作原理,然后空谈抽象概念。 知识,也就是你问他问题,他能输出正确的回答,这只是一个简单的输出token的过程。输出得准了,就是知识,输出不准了,…...
科普文:微服务之Spring Cloud Alibaba组件Nacos一致性协议Distro+Raft概叙
一、概要 Nacos是阿里开放的一款中间件,它主要提供三种功能:持久化节点注册,非持久化节点注册和配置管理。 二、一致性协议 - AP/CP Nacos不是纯粹的AP服务,也不是纯粹的CP服务,而是两者同时支持。 这要从服务注册…...
python合并音视频-通过ffmpeg合并音视频
🌈所属专栏:【python】✨作者主页: Mr.Zwq✔️个人简介:一个正在努力学技术的Python领域创作者,擅长爬虫,逆向,全栈方向,专注基础和实战分享,欢迎咨询! 您的…...
Yolov8添加ConvNetV1和V2模块
Yolov8添加ConvNet模块 1 ConvNet系列相关内容 (1)2022 论文地址:A ConvNet for the 2020s Code Link 如下图所示,精度、效率、尺寸都很不错。 论文的摘要如下: 视觉识别的“咆哮的 20 年代”始于视觉注意力 &…...
十个常见的 Python 脚本 (详细介绍 + 代码举例)
1. 批量重命名文件 介绍: 该脚本用于批量重命名指定目录下的文件,例如将所有 ".txt" 文件重命名为 ".md" 文件。 import osdef batch_rename(directory, old_ext, new_ext):"""批量重命名文件扩展名。Args:directory: 要处理…...
【C语言】详解feof函数和ferror函数
文章目录 前言1. feof1.1 feof函数原型1.2 正确利用函数特性读写文件1.2.1 针对文本文件1.2.2 针对二进制文件 1.3 feof函数的原理1.4 feof函数实例演示 2. ferror2.1 ferror函数原型 前言 或许我们曾在网络上看过有关于feof函数,都说这个函数是检查文件是否已经读…...
ValueListenableBuilder 和 addListener 在 ChangeNotifier的区别
1、前言 ValueListenableBuilder 和 addListener 在 ChangeNotifier 中有不同的用途和用法,适用于不同的场景。它们的主要区别在于它们如何监听和响应状态变化,以及它们的用法和特性。 2、ValueListenableBuilder用法 ValueListenableBuilder 是一个 …...
ScriptEcho:AI赋能的前端代码生成神器
ScriptEcho:AI赋能的前端代码生成神器 在前端开发中,如果你总是觉得写代码太费时费力,那么 ScriptEcho 将成为你的救星。这个 AI 代码生成平台不仅能帮你省下大量时间,还能让你轻松愉快地写出生产级代码。本文将带你了解 ScriptEc…...
TypeError: ‘float’ object is not iterable 深度解析
TypeError: ‘float’ object is not iterable 深度解析与实战指南 在Python编程中,TypeError: float object is not iterable是一个常见的错误,通常发生在尝试对浮点数(float)进行迭代操作时。这个错误表明代码中存在类型使用不…...
灵茶八题 - 子序列 +w+
灵茶八题 - 子序列 w 题目描述 给你一个长为 n n n 的数组 a a a,输出它的所有非空子序列的元素和的元素和。 例如 a [ 1 , 2 , 3 ] a[1,2,3] a[1,2,3] 有七个非空子序列 [ 1 ] , [ 2 ] , [ 3 ] , [ 1 , 2 ] , [ 1 , 3 ] , [ 2 , 3 ] , [ 1 , 2 , 3 ] [1],[…...
为什么美元债务会越来越多?
美元债务规模持续膨胀,其背后原因复杂多样,可归结为以下几个主要因素: 财政赤字和刺激政策是导致美元债务增加的重要原因。美国政府长期面临财政赤字问题,支出远超收入,为弥补这一缺口,政府不得不大量发行…...
二维凸包算法 Julia实现
问题描述:给定平面上 n n n 个点的集合 Q Q Q,求其子集 P P P 构成 Q Q Q 的凸包,即 ∀ p ∈ Q , ∃ p 0 , p 1 , p 2 ∈ P \forall p \in Q, \exist p_0, p_1, p_2 \in P ∀p∈Q,∃p0,p1,p2∈P 使得点 p p p 在以点 p 0 , p 1 …...
python dash框架
Dash 是一个用于创建数据分析型 web 应用的 Python 框架。它由 Plotly 团队开发,并且可以用来构建交互式的 web 应用程序,这些应用能够包含图表、表格、地图等多种数据可视化组件。 Dash 的特点: 易于使用:Dash 使用 Python 语法…...
2.外部中断(EXTI)
理论 NVIC:嵌套向量中断控制器(解释教程) 外部通用中断线(EXTI0~EXTI15):每个GPIO设置成中断模式,与中断控制器连接的线 外部中断触发方式 上升沿触发、下降沿触发、双边沿触发 外部中断触发函数 在stm32f1xx_it.c文件…...
Python | SyntaxError: invalid syntax 深度解析
Python | SyntaxError: invalid syntax 深度解析 在Python编程中,SyntaxError: invalid syntax是一个常见的错误,它表明Python解释器在尝试解析代码时遇到了语法问题。这个错误通常是由于代码中存在拼写错误、缺少符号(如括号、冒号或逗号&a…...
付费进群系统源码原版最新修复全开源版
付费进群,和平时所见到的别人拉你进群是不一样的,付费进群需要先缴费以后,才会看到群的二维码,扫码进群或者是长按二维码图片识别进群,付费进群这个功能广泛应用于拼多多的砍价群,活动的助力群,…...
Docker容器部署的SpringBoot项目jar包,上传文件但是找不到路径的问题
在docker容器内部署的jar包运行后,请求访问都没有问题,在文件上传时,发现上传图片接口响应成功,但是图片路径报404错误,发现找不到路径。 在服务器上查看也没有找到相关图片。 原因: 启动docker镜像时没…...
云计算学习——5G网络技术
系列文章目录 提示:仅用于个人学习,进行查漏补缺使用。 Day1 网络参考模型 Day2 网络综合布线与应用 Day3 IP地址 Day4 华为eNSP网络设备模拟器的基础安装及简单使用 Day5 交换机的基本原理与配置 Day6 路由器的原理与配置 Day7 网络层协议介绍一 Day8 传…...
matlab仿真 信道编码和交织(上)
(内容源自详解MATLAB/SIMULINK 通信系统建模与仿真 刘学勇编著第八章内容,有兴趣的读者请阅读原书) clear all N10;%信息比特的行数 n7;%hamming码组长度n2^m-1 m3;%监督位长度 [H,G]hammgen(m);%产生(n,n-…...
基于YOLOv8的高压输电线路异物检测系统
基于YOLOv8的高压输电线路异物检测系统 (价格88) 包含 【“鸟窝”,“风筝”,“气球”,“垃圾”】 4个类 通过PYQT构建UI界面,包含图片检测,视频检测,摄像头实时检测。 (该系统可以根据数…...
23款奔驰GLS450加装原厂电吸门配置,提升车辆舒适性和便利性
今天是一台22款奔驰GLS450,车主是佛山的 以前被不良商家坑了 装了副厂的电吸门 刚开始就很正常 用了半年之后 就开始开不了门,被锁在里面,刚开始车主以为是零件坏了 后来越来越频繁,本来是为了家里老人小孩关门方便而升级的&#…...
git操作流程笔记
1、在本地项目文件夹右击鼠标点击Git Bash Here 2、输入git init,这个目录变成git可以管理的仓库,会出现一个.git文件夹,如果没出现的话需要选择“显示隐藏文件”(不会的同学自行百度一下) 3、绑定本地仓库与远程仓库…...
【QT】常用控件-上
欢迎来到Cefler的博客😁 🕌博客主页:折纸花满衣 目录 👉🏻QWidgetenabledgeometryrect制作上下左右按钮 window frame 的影响window titlewindowIcon代码示例: 通过 qrc 管理图片作为图标 windowOpacitycursor使用qrc自…...
帮助网站提升用户参与度的5个WordPress插件
仅靠编写精彩的内容、设计精美的图像和创建简化的客户旅程不足以提高网站参与度。您需要让用户在首次访问后继续与您的网站互动并成为回访者,才能真正吸引您所追求的兴趣。 幸运的是,对于 WordPress 用户来说,有数百种工具可用于提高用户参与…...
理解 Python 中的 @wraps:保留函数元数据
一.介绍 在本文中,我们将了解 wraps。在 Python 中使用装饰器时,您可能会遇到原始函数的元数据丢失的情况。这时,functools 模块中的 wraps 装饰器就可以派上用场了。让我们深入了解 wraps 的作用及其重要性。 二.简单装饰器的问题 首先&a…...
cjson
文章目录 概述编译cjson_test 小结 概述 在网络传输中,网络数据序列化,常用的有那么几种,json,protobuf都是很常用的,这一篇来写下json。 Json常用的有几个,rapidjson,jsoncpp,还有…...
Docker data root 目录更改
有时候受限于系统根目录空间的限制,需要将 docker data root 目录更改为其它目录,如单独挂载一个磁盘或存储。本篇文章介绍如何操作。 修改docker 工作目录 修改配置文件/etc/docker/daemon.json(在19.x 版本之前使用grapth) {&q…...
吴江城乡建设局网站/品牌宣传有哪些途径
最近一直在看关于重构的经典:《重构——改善既有代码的设计》,也算是颇有所得。一直想写写感悟,奈何书写的实在太好,感悟的都是里面的原话,实在没必要。昨天同学写了一个简单的小程序,让我帮忙找bug。我一看…...
公司推进企业安全文化建设/优化设计答案六年级上册语文
[函数名称] 二值图像腐蚀函数CorrosionProcess(WriteableBitmap src) [算法说明] 二值图像腐蚀操作属于图像形态学的范畴,形态学运算是只针对二值图像进行,并依据数学形态学(Mathermatical Morphogy)集合论方法发展起来的数字图…...
方圆网站建设/seo外包公司排名
好的,下面是在 Qt 中实现 TCP 通讯的一些示例代码: 建立一个简单的 TCP 服务器:#include <QTcpServer> #include <QTcpSocket> #include <QDebug>int main(int argc, char *argv[]) {QTcpServer server;if (!server.listen(QHostAddress::Any, 1234)) {qD…...
网站怎么做弹框/网站网页的优化方法
更多功能测试以及全套学习路线图均在专栏,↑↑戳进去领取~ 系列文章目录 🍑软件测试功能到自动化学习路线图,2022年最新版技术栈 🍑软件测试01:从了解测试岗位职能和测试流程开始,附作业 🍑软件…...
wordpress数据库设置密码/竞价排名什么意思
前言 期末到了,上课老师讲的云里雾里,很慌某些老师给一些所谓复习题其实是考试题的题,害人害己,白交学费。想学东西找不到途径?MOOC所谓的没有围墙,除了精品课程其他大都在念PPT,并未深入浅出的…...
微信移动网站建设/搜索引擎优化策略有哪些
Unity3D-Tag 标签简介与应用 转载自Unity3D(通过Tag 标签查找物体) 1.Tag 简介 Tag 标签,就是一个标签。标签可以起到标识,区分的作用。 同一类的模型,我们可以根据需要给他们设置成统一的标签。 2.给模型添加Tag 标签 选中一个模型&…...