大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
这篇文章主要讲解了“Cgroup限制CPU、IO、内存以及linux系统中的调度方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Cgroup限制CPU、IO、内存以及linux系统中的调度方法”吧!
成都创新互联是专业的礼县网站建设公司,礼县接单;提供成都做网站、网站设计,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行礼县网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
一、CPU
1、0核利用
思路: 0核是必须要保护的,否则各种系统命令、喂狗都可能出问题;
设想是把某个实例,比如SD,绑定到0核,并通过cgroup功能限制CPU负载,比如最多使用80%的0核CPU;
问题: cgroup被裁掉了,需要安装; 需要分析和测试cgroup是否会影响调度顺序;
2、其它核
思路:其它实例,绑定到除0核和dpdk核之外的所有核,也就是允许在这些核上迁移,由操作系统调度;
问题:按照以前的经验,操作系统调度进行核迁移是不会很快的,在高负载的时候容易瞬间CPU冲顶导致呼损;
v4用法:
service cgconfig start #开启cgroups服务
chkconfig cgconfig on #开启启动
v5用法
systemctl start cgconfig.service
systemctl enable cgconfig.service
systemctl is-enable cgconfig.service
v5系统安装cgroup,安装libcgroup-0.41-13.el7.x86_64.rpm //v5系统已经安装
libcgroup-devel-0.41-13.el7.x86_64.rpm
libcgroup-tools-0.41-13.el7.x86_64.rpm
https://segmentfault.com/a/1190000008323952
cgroup.procs:只读文件,显示当前cgroup下的所有进程
CFS完全公平调度策略
cpu.cfs_period_us:用来配置时间周期长度,单位是us,取值范围1000~1000000:1ms ~ 1s
cpu.cfs_quota_us:用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数,单位us,最小值为1000:1ms; t > 1000
tasks中所有任务总CPU占用率 = cpu.cfs_quota_us/cpu.cfs_period_us
只有这两个文件同时有效,表示cfs限制开启,此时才能将cfs调度策略的进程写进tasks中,否则参数无效echo: write error: Invalid argument
RT实时调度策略
cpu.rt_period_us :统计CPU使用时间的周期,单位us,最小值为1 ,t > 1
cpu.rt_runtime_us:周期内允许任务使用单个CPU核的时间,如果系统中有多个核,则可以使用核倍数的时间,单位us,最小值为0
tasks中所有任务总CPU占用率 = cpu.rt_runtime_us*CPU核数 / cpu.rt_period_us
只有这两个文件同时有效,表示rt限制开启,此时才能将rt调度策略的进程写进tasks中,
如果同时配置RT和CFS有效,A(RT类型线程总和)、B(CFS类型线程总和)两者分别工作,
对tasks中的对应的线程属性进行限制
假设RT和CFS均设置CPU占用率为80%,那么A、B均占用80%,
两者加起来即160%,如果tasks中的线程共核,那么: A + B = 100%
至于A、B的具体值,由于A为RT调度优先抢占CPU,通常是A=80%,B=20%
两种调度策略均通过配置cgconfig.conf生效
cpu.shares
cpu.stat
notify_on_release
release_agent
tasks
-rw-r--r-- 1 root root 0 Aug 12 17:09 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Aug 12 17:09 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Aug 12 17:09 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Aug 12 17:09 cpu.rt_runtime_us
v4环境:
service sbcd restart ===不要存在/cgroup/cpu/路径否则service cgconfig restart失败导致单板重启
v5环境:
当系统
遗留问题:
1、如何配置cgroup,v5系统设置开机启动,无法正常启动
答:已经解决,修改cgconfig.conf需要重启单板一次才能生效
2、实时调度的cpu.rt_period_us 、cpu.rt_runtime_us参数粒度选取;
粒度太大,直观感觉占用cpu过大,系统命令响应变慢
答:写一个测试程序(两种调度方式),
a、观察在不同粒度下执行相同操作(1w、10w、100w次循环)所需时间
b、观察程序的调度次数
4、调度方式发生改变
背景:发现会修改SD下线程的调度方式 5号(mspe),9号(mpe)机均出现
Q1:sbc中线程的调度策略,有什么决定
原因:sd继承shell进程,将任务默认写进/sys/fs/cgroup/cpu/user.slice
user.slice分组中rt=0,导致SD中设置线程调度属性失败(sched_setscheduler接口返回 -1)
解决方法:1、裁剪或订制user.slice、system.slice,经试验user.slice.rt + system.slice.rt < 90%可行
进一步论证:user.slice.rt + system.slice.rt +test.rt < 100
2、在代码中实现,先进行写文件设置,后进行调度属性设置,实验可行
5、通过配置cgconfig.conf ,/cgconfig.d/test.conf,可以解决调度策略发生改变问题
Q1:systemctl restart cgconfig.service 命令失败
测试问题收尾:
1、实时调度的cpu.rt_period_us 、cpu.rt_runtime_us参数粒度选取;
粒度太大,直观感觉占用cpu过大,系统命令响应变慢
解决方案:写一个测试程序(cfs调度,系统命令也cfs调度方式)
1、观察程序的调度次数
sbc占用80%,linux_endless占用20%
粒度小cpu.rt_period_us=100000 cpu.rt_runtime_us=20000
粒度大cpu.rt_period_us=1000000 cpu.rt_runtime_us=200000
相同时间:
粒度大sbc调用次数 > 粒度小sbc调用次数
粒度大linux_endless调用次数 < 粒度小linux_endless调用次数
因此,选择粒度小的cpu参数,要优先保证操作系统调度
#总核数 = 物理CPU个数 * 每个物理CPU的核数
#总逻辑CPU数 = 总核数 * 超线程数
#查看物理CPU个数
cat /proc/cpuinfo | grep "physical id" |sort | uniq |wc -l
#查看每个物理CPU中的core的个数
cat /proc/cpuinfo | grep "cpu cores" | uniq
#查看逻辑CPU个数
cat /proc/cpuinfo | grep "processor" | wc -l
#查看超线程是否打开判断
cat /proc/cpuinfo | grep "sibling" | uniq ,cat /proc/cpuinfo | grep "cpu cores" | uniq
如果"siblings"和"cpu cores"一致,则说明不支持超线程,或者超线程未打开;
如果"siblings"是"cpu cores"的两倍,则说明支持超线程,并且超线程已打开
#查看CPU是32还是64位运行模式
getconf LONG_BIT
执行结果:64
注意:如果结果是32,代表是运行在32位模式下,但不代表CPU不支持64bit。4.CPU是32还是64位运行模式
注意:如果结果是32,代表是运行在32位模式下,但不代表CPU不支持64bit。
一般情况:逻辑CPU的个数 = 物理CPU个数 * 每个cpu的核数。如果不相等的话,则表示服务器的CPU支持超线程技术
1、物理CPU:实际Server中插槽上的CPU个数
物理cpu数量,可以数不重复的 physical id 有几个:cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
2、cpu核数:一块CPU上面能处理数据的芯片组的数量、比如现在的i5 760,是双核心四线程的CPU、而 i5 2250 是四核心四线程的CPU
cpu核数查看方法:cat /proc/cpuinfo | grep "cpu cores" | uniq
3、逻辑CPU:/proc/cpuinfo 这个文件是用来存储cpu硬件信息的(信息内容分别列出了processor 0 – n 的规格。而这里的n是逻辑cpu数量)
一个cpu可以有多核,加上intel的超线程技术(HT), 可以在逻辑上再分一倍数量的cpu core出来,所以:
cat /proc/cpuinfo| grep "processor"| wc -l
逻辑CPU数量 = 物理cpu数量 * cpu cores 这个规格值 * 2(如果支持并开启ht)
注意:Linux下top查看的CPU也是逻辑CPU个数
查看每个逻辑cpu当前的占用率 top ,然后按下数字 1;
top -H -p xxxx //看xxx进程下的线程CPU占用率
perf top -Cx // 看具体函数的在CPUx占用率
绑定进程到CPU核上:taskset -cp cpu_id pid
查看进程位于哪个cpu核上:taskset -p pid
查看进程的调度策略:ps -eo class,cmd
TS SCHED_OTHER
FF SCHED_FIFO
RR SCHED_RR
B SCHED_BATCH
ISO SCHED_ISO
systemctl list-unit-files | grep enabled //v5系统执行服务命令systemctl
查看挂载cgroup
lssubsys -am
手工挂载cpu
mount -t cgroup -o cpu,cpuacct cpu /cgroup/cpu
[root@localhost cgroup]# systemctl status cgconfig.service
● cgconfig.service - Control Group configuration service
Loaded: loaded (/usr/lib/systemd/system/cgconfig.service; disabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Thu 2018-08-09 10:22:51 UTC; 19min ago
Process: 4076 ExecStart=/usr/sbin/cgconfigparser -l /etc/cgconfig.conf -L /etc/cgconfig.d -s 1664 (code=exited, status=101)
Main PID: 4076 (code=exited, status=101)
Aug 09 10:22:51 localhost.localdomain systemd[1]: Starting Control Group configuration service...
Aug 09 10:22:51 localhost.localdomain cgconfigparser[4076]: /usr/sbin/cgconfigparser; error loading /etc/cgconfig.conf: Cgroup mounting failed
Aug 09 10:22:51 localhost.localdomain systemd[1]: cgconfig.service: main process exited, code=exited, status=101/n/a
Aug 09 10:22:51 localhost.localdomain cgconfigparser[4076]: Error: cannot mount cpu,cpuacct to /cgroup/cpu: Device or resource busy
Aug 09 10:22:51 localhost.localdomain systemd[1]: Failed to start Control Group configuration service.
Aug 09 10:22:51 localhost.localdomain systemd[1]: Unit cgconfig.service entered failed state.
Aug 09 10:22:51 localhost.localdomain systemd[1]: cgconfig.service failed.
[root@localhost cgroup]# cgclear
ps -ef | grep sleep # 找出 sleep 1000 的pid, 这里假设是 1234
chrt -p 1234 # 可以查看 pid=1234 的进程的 调度策略, 输入如下:
pid 1234's current scheduling policy: SCHED_OTHER
pid 1234's current scheduling priority: 0
chrt -p -f 10 1234 # 修改调度策略为 SCHED_FIFO, 并且优先级为10
chrt -p 1234 # 再次查看调度策略
pid 1234's current scheduling policy: SCHED_FIFO
pid 1234's current scheduling priority: 10
chrt -p -r 10 4572 # 修改调度策略为 SCHED_RR, 并且优先级为10
chrt -p 4572
pid 4572's current scheduling policy: SCHED_RR
pid 4572 的当前调度优先级:10
chrt -p -o 0 1234 在修改为SCHED_OTHER
输入chrt 列出可选参数
获取系统实时进程调度配置
sysctl -n kernel.sched_rt_period_us #实时进程调度的单位CPU时间 1 秒
sysctl -n kernel.sched_rt_runtime_us #实时进程在 1 秒中实际占用的CPU时间, 0.95秒
设置实时进程占用CPU时间
上面的默认设置中, 实时进程占用 95% 的CPU时间. 如果觉得占用的太多或太少, 都是可以调整的.比如:
sysctl -w kernel.sched_rt_runtime_us=900000 # 设置实时进程每1秒中只占0.9秒的CPU时间
kernel.sched_rt_runtime_us = 900000
sysctl -n kernel.sched_rt_runtime_us
900000
cgroup 中的设置
整体设置是针对整个系统的, 我们也可以通过 cgroup 来对一组进程的CPU资源进行控制.
如果想在 cgroup 中对 sched_rt_period_us 和 sched_rt_runtime_us 进行控制, 需要内核编译选项 CONFIG_RT_GROUP_SCHED=y
cat /boot/config-`uname -r`
查看 CONFIG_RT_GROUP_SCHED 是否启用
/*其他模块*/
1、blkio模块、相关参数及其含义:
1.1. 权重比例
blkio.weight
设定默认使用的权重比例,取值范围:100—1000。此值会被blkio.weight_device值覆盖。
echo 500 > blkio.weight
blkio.weight_device
设定指定设备的使用的权重比例,取值范围:100—1000。此值会覆盖blkio.weight设定值。该值的格式为:major:minor weight,即,主设备号:次设备号 权重。例如:设定硬盘sda的访问权重为500.
ps:
# ll /dev/sda
brw-rw---- 1 root disk 8, 0 Aug 15 15:42 /dev/sda
主设备号为8,次设备号为0.
echo 8:0 500 > blkio.weight_device //测试发现此设备填非0,写失败
echo 3 > /proc/sys/vm/drop_caches //清文件系统缓存,很重要,否则可能看到io的读取速率为0
cgexec -g "blkio:test1" dd bs=1M count=4096 if=file1 of=/dev/null
cgexec -g "blkio:test2" dd bs=1M count=4096 if=file2 of=/dev/null //注意:读取设备不能相同,否则无法看见速率差异
1.2. I/O 使用上限
blkio.throttle.read_bps_device / blkio.throttle.write_bps_device
指定 cgroup 中某设备每秒钟读/写数据的字节上限。其格式为 major:minor bytes_per_second。
blkio.throttle.read_iops_device / blkio.throttle.write_iops_device
指定 cgroup 中某设备每秒钟可以执行的读/写请求数上限。其格式为major:minor operations_per_second。
测试:
1、echo '8:0 1000000' > blkio.throttle.read_bps_device //设置分组读取数据为1M/s
2、echo 3 > /proc/sys/vm/drop_caches //清文件系统缓存,很重要
3、dd if=/dev/sda of=/dev/null &
4、iotop -p cmd2_pid //查看进程读取磁盘速率
5、将cmd2_pid加入task分组查看速率变化
//C代码只需要调用系统接口read读取磁盘设备即可
1.3. 统计参数
blkio.reset_stats:向该文件中写入一个整数,可以重置该 cgroup 中的统计计数。
blkio.time :统计 cgroup 对具体设备的 I/O 访问时间。单位为毫秒(ms)
blkio.sectors :统计 cgroup 对具体设备的扇区读写数。
blkio.io_serviced:统计 cgroup 对具体设备的读写操作数。内容有四个字段:major, minor,operation (read, write, sync, or async)和 number(表示操作的次数)。
blkio.io_service_bytes:统计 cgroup对具体设备的读写字节数。内容有四个字段:major, minor, operation (read, write, sync, or async)和 bytes(表示传输的字节数)。
blkio.io_service_time:统计 cgroup 对指定设备的 I/O 操作发送请求和完成请求之间的时间。条目有四个字段:major, minor, operation 和 time,其中 time 的单位为纳秒(ns)。
blkio.io_wait_time:统计 cgroup 对具体设备的 I/O 操作在队列调度中等待的时间。内容有四个字段:major,minor, operation 和 time,其中 time 的单位为纳秒(ns),这意味着对于ssd硬盘也是有意义的。
blkio.io_merged:统计 cgroup 将 BIOS 请求合并到 I/O 操作请求的次数。内容有两个字段:number和 operation。
blkio.io_queued:统计I/O 操作排队的请求数。内容有两个字段:number 和 operation(read, write, sync, or async)。
blkio.throttle.io_serviced:统计 cgroup 对具体设备的读写操作数。blkio.io_serviced 与blkio.throttle.io_serviced的不同之处在于,CFQ 调度请求队列时,前者不会更新。
内容有四个字段:(read, write, sync, or async)和 number(表示操作的次数)。
blkio.throttle.io_service_bytes:统计 cgroup对具体设备的读写字节数。blkio.io_service_bytes 与blkio.throttle.io_service_bytes 的不同之处在于,CFQ 调度请求队列时,前者不会更新。内容有四个字段:(read, write, sync, or async)和 bytes(表示传输的字节数)。
2、memory模块、相关参数及其含义:
2.1、参数概要:
cgroup.event_control #用于eventfd的接口
memory.usage_in_bytes #显示当前已用的内存
memory.limit_in_bytes #设置/显示当前限制的内存额度
memory.failcnt #显示内存使用量达到限制值的次数
memory.max_usage_in_bytes #历史内存最大使用量
memory.soft_limit_in_bytes #设置/显示当前限制的内存软额度
memory.stat #显示当前cgroup的内存使用情况
memory.use_hierarchy #设置/显示是否将子cgroup的内存使用情况统计到当前cgroup里面
memory.force_empty #触发系统立即尽可能的回收当前cgroup中可以回收的内存
memory.pressure_level #设置内存压力的通知事件,配合cgroup.event_control一起使用
memory.swappiness #设置和显示当前的swappiness
memory.move_charge_at_immigrate #设置当进程移动到其他cgroup中时,它所占用的内存是否也随着移动过去
memory.oom_control #设置/显示oom controls相关的配置
memory.numa_stat #显示numa相关的内存
2.2、属性限制
memory.force_empty :当向memory.force_empty文件写入0时(echo 0 > memory.force_empty),将会立即触发系统尽可能的回收该cgroup占用的内存。该功能主要使用场景是移除cgroup前(cgroup中没有进程),先执行该命令,可以尽可能的回收该cgropu占用的内存,这样迁移内存的占用数据到父cgroup或者root cgroup时会快些。
memory.swappiness :该文件的值默认和全局的swappiness(/proc/sys/vm/swappiness)一样,修改该文件只对当前cgroup生效,其功能和全局的swappiness一样
有一点和全局的swappiness不同,那就是如果这个文件被设置成0,就算系统配置的有交换空间,当前cgroup也不会使用交换空间。
memory.use_hierarchy:该文件内容为0时,表示不使用继承,即父子cgroup之间没有关系;当该文件内容为1时,子cgroup所占用的内存会统计到所有祖先cgroup中。
如果该文件内容为1,当一个cgroup内存吃紧时,会触发系统回收它以及它所有子孙cgroup的内存。
注意: 当该cgroup下面有子cgroup或者父cgroup已经将该文件设置成了1,那么当前cgroup中的该文件就不能被修改。
memory.soft_limit_in_bytes:有了hard limit(memory.limit_in_bytes),为什么还要soft limit呢?hard limit是一个硬性标准,绝对不能超过这个值,而soft limit可以被超越,既然能被超越,要这个配置还有啥用?先看看它的特点当系统内存充裕时,soft limit不起任何作用当系统内存吃紧时,系统会尽量的将cgroup的内存限制在soft limit值之下(内核会尽量,但不100%保证)
从它的特点可以看出,它的作用主要发生在系统内存吃紧时,如果没有soft limit,那么所有的cgroup一起竞争内存资源,占用内存多的cgroup不会让着内存占用少的cgroup,这样就会出现某些cgroup内存饥饿的情况。如果配置了soft limit,那么当系统内存吃紧时,系统会让超过soft limit的cgroup释放出超过soft limit的那部分内存(有可能更多),这样其它cgroup就有了更多的机会分配到内存。所以,这其实是系统内存不足时的一种妥协机制,给次等重要的进程设置soft limit,当系统内存吃紧时,把机会让给其它重要的进程。
注意: 当系统内存吃紧且cgroup达到soft limit时,系统为了把当前cgroup的内存使用量控制在soft limit下,在收到当前cgroup新的内存分配请求时,就会触发回收内存操作,所以一旦到达这个状态,就会频繁的触发对当前cgroup的内存回收操作,会严重影响当前cgroup的性能。
memory.oom_control:内存超限之后的 oom 行为控制。
查看oom killer设置,不能用vi编辑,只能用echo,但是根路径下的memory.oom_control无法设置
[root@localhost test]# echo 1 > memory.oom_control
[root@localhost test]# cat memory.oom_control
oom_kill_disable 1
under_oom 0 //只读字段
关闭oom killer:
设置 oom_kill_disable 为 1。(0 为开启)当触发oom时,但是开关关闭时,对应的线程仍然无法调度,出现D状态
cgroup.event_control:实现OOM的通知,当OOM发生时,可以收到相关的事件
memory.move_charge_at_immigrate:当一个进程从一个cgroup移动到另一个cgroup时,默认情况下,该进程已经占用的内存还是统计在原来的cgroup里面,不会占用新cgroup的配额,但新分配的内存会统计到新的cgroup中(包括swap out到交换空间后再swap in到物理内存中的部分)。我们可以通过设置memory.move_charge_at_immigrate让进程所占用的内存随着进程的迁移一起迁移到新的cgroup中。
方法:
enable: echo 1 > memory.move_charge_at_immigrate
disable:echo 0 > memory.move_charge_at_immigrate
注意: a、就算设置为1,但如果不是thread group的leader,这个task占用的内存也不能被迁移过去。换句话说,如果以线程为单位进行迁移,必须是进程的第一个线程,如果以进程为单位进行 迁移,就没有这个问题。
当memory.move_charge_at_immigrate为0时,就算当前cgroup中里面的进程都已经移动到其它cgropu中去了,由于进程已经占用的内存没有被统计过去,当前cgroup有可能还占用很 多内存,当移除该cgroup时,占用的内存需要统计到谁头上呢?答案是依赖memory.use_hierarchy的值,如果该值为0,将会统计到root cgroup里;如果值为1,将统计到它的父cgroup 里面。
b、当前进程分组时,如果进程使用的内存(memory.usage_in_bytes)大于目标分组的内存限制(memory.limit_in_bytes),则迁移失败
[root@localhost memory]# echo 5750 > test_1/tasks
[root@localhost memory]# cat test_1/memory.usage_in_bytes
106496
[root@localhost memory]# cat test_2/memory.usage_in_bytes
0
[root@localhost memory]# cd test_2
[root@localhost test_2]# cat memory.limit_in_bytes
9223372036854771712
[root@localhost test_2]# echo 10240 > memory.limit_in_bytes
[root@localhost test_2]# echo 5750 > tasks
-bash: echo: 写错误: 无法分配内存
[root@localhost test_2]# echo 1024000 > memory.limit_in_bytes
[root@localhost test_2]# echo 5750 > tasks
[root@localhost test_2]# cat memory.usage_in_bytes
106496
c、当进程正在申请内存时,迁移分组,已经申请内存统计不会迁移
2.3、内存限制
memory.memsw.limit_in_bytes:内存+swap空间使用的总量限制。
memory.limit_in_bytes:内存使用量限制。
memory.memsw.limit_in_bytes 必须大于或等于 memory.limit_in_byte。
要解除内存限制,把对应的值设为 -1 即可。
这种方式限制进程内存占用会有个风险。当进程试图占用的内存超过限制时,会触发 oom ,导致进程直接被杀,从而造成可用性问题。即使关闭控制组的 oom killer,在内存不足时,进程虽然不会被杀,但是会长时间进入 D 状态(等待系统调用的不可中断休眠),并被放到 OOM-waitqueue 等待队列中, 仍然导致服务不可用。因此,用 memory.limit_in_bytes 或 memory.memsw.limit_in_bytes 限制进程内存占用仅应当作为一个保险,避免在进程异常时耗尽系统资源。如,预期一组进程最多会消耗 1G 内存,那么可以设置为 1.5G 。这样在发生内存泄露等异常情况时,可以避免造成更严重问题。
注意:如果当前分组下已经存在task任务,修改memory.limit_in_bytes的值必须大于memory.usage_in_bytes的值,否则修改失败
[root@localhost test_2]# cat memory.usage_in_bytes
106496
[root@localhost test_2]# echo 106494 > memory.limit_in_bytes
-bash: echo: 写错误: 设备或资源忙
[root@localhost test_2]# echo 106498 > memory.limit_in_bytes
[root@localhost test_2]# cat memory.limit_in_bytes
106496 //数值不一定精确写入
[root@localhost test_2]#
2.4、内存资源审计
memory.memsw.usage_in_bytes:当前 cgroup 的内存+ swap 的使用量。
memory.usage_in_bytes:当前 cgroup 的内存使用量。
memory.max_usage_in_bytes:cgroup 最大的内存+ swap 的使用量。
memory.memsw.max_usage_in_bytes:cgroup 的最大内存使用量。
感谢各位的阅读,以上就是“Cgroup限制CPU、IO、内存以及linux系统中的调度方法”的内容了,经过本文的学习后,相信大家对Cgroup限制CPU、IO、内存以及linux系统中的调度方法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!