ARM 软中断指令SWI

前面我们学习ARM工作模式中,处理器模式切换可以通过软件控制进行切换,即修改CPSR模式位,但这是在特权模式下,当我们处于用户模式下,是没有权限实现模式转换的。若想实现模式切换,只能由另一种方法来实现,即通过外部中断或是异常处理过程进行切换。于是ARM指令集中提供了两条产生异常的指令,通过这两条指令可以用软件的方法实现异常,其中一个就中断指令SWI 。

 

一、软件中断

       软中断是利用硬件中断的概念,用软件方式进行模拟,实现从用户模式切换到特权模式并执行特权程序的机制。

       硬件中断是由电平的物理特性决定,在电平变化时引发中断操作,而软中断是通过一条具体指令SWI,引发中断操作,也就是说用户程序里可以通过写入SWI指令来切换到特权模式,当CPU执行到SWI指令时会从用户模式切换到管理模式下,执行软件中断处理。由于SWI指令由操作系统提供的API封装起来,并且软件中断处理程序也是操作系统编写者提前写好的,因此用户程序调用API时就是将操作权限交给了操作系统,所以用户程序还是不能随意访问硬件。

    

     软件中断指令(Software Interrupt, SWI用于产生软中断,实现从用户模式变换到管理模式,CPSR保存到管理模式的SPSR中,执行转移到SWI向量。在其他模式下也可以使用SWI指令,处理器同样切换到管理模式。

1SWI指令格式如下:

   SWI{cond} immed_24

其中:

immed_24  24位立即数,值为从0――16777215之间的整数。

       SWI指令后面的24立即数是干什么用的呢?用户程序通过SWI指令切换到特权模式,进入软中断处理程序,但是软中断处理程序不知道用户程序到底想要做什么?SWI指令后面的24位用来做用户程序和软中断处理程序之间的接头暗号。通过该软中断立即数来区分用户不同操作,执行不同内核函数。如果用户程序调用系统调用时传递参数,根据ATPCSC语言与汇编混合编程规则将参数放入R0~R4即可。

 

2、指令举例

      使用SWI指令时,通常使用以下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。SWI异常中断处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。

1)、指令中24位的立即数指定了用户请求的服务类型,中断服务的参数通过通用寄存器传递。

如下面这个程序产生一个中断号位12 的软中断:

MOV R0,#34                    ;设置功能号为34

SWI 12                              ;产生软中断,中断号为12

2)、指令中的24位立即数被忽略,用户请求的服务类型有寄存器R0的值决定,参数通过其他的通用寄存器传递。

如下面的例子通过R0传递中断号,R1传递中断的子功能号:

MOV R0, #12                  ;设置12号软中断

MOV R1, #34                  ;设置功能号为34

SWI  0


      下面的例子通过系统调用函数int led_on(int led_no)实现点亮第led_no LED灯,由于C语言里没有SWI 指令对应的语句,因此这儿要用到C语言与汇编混合编程,led_on函数里将参数led_no的值传递给R0,通过软中断SWI指令切换到软中断管理模式,同时R0 软中断方式点亮LED灯,用户通过SWI  #1指令可以点灯,具体点亮哪个灯,通过R0保存参数传递,如果亮灯成功返回对应LED号。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #define __led_on_swi_no         1                 // 软中断号1,调用管理模式下的do_led_on函数  
  2. int led_on(int led_no){  
  3.   
  4.          int ret;                                 // 返回值  
  5.           __asm{                                   // 由于C程序中没有SWI对应表达式,所以使用混合编程  
  6.                    mov  r0, led_no                  // 根据ATPCS规则,r0存放第一个参数  
  7.                    swi    __led_on_swi_no           // 产生SWI软中断,中断号为__led_on_swi_no  
  8.   
  9.                    mov  ret, r0                   // 软中断处理结束,取得中断处理返回值,传递给ret变量  
  10.          }  
  11.   
  12.          return ret;                              // 将ret返回给调用led_on的语句  
  13. }  


二、软中断处理

   CPU执行到swi xxx执行后,产生软件中断,由异常处理部分知识可知,软中断产生后CPU将强制将PC的值置为异常向量表地址0x08,在异常向量表0x08处安放跳转指令b HandleSWI,这样CPU就跳往我们自己定义的HandleSWI处执行。

1--保护现场 

  软中断处理中通过STMFD  SP!, {R0-R12,  LR}      要保存程序执行现场,将R0~R12通用寄存器数据保存在管理模式下SP栈内,LR由硬件自动保存软中断指令下一条指令的地址(后面利用LR的地址取得SWI指令编码),该寄存器值也保存在SP栈内,将来处理完毕之后返回;

2--获取SWI指令编码

  SWI指令编码知识可知,SWI指令低24位保存有软中断号,通过LDR R4, [LR, #-4]指令,取得SWI指令编码(LR为硬件自动保存SWI xxx指令的下一条指令地址,LR – 4就是SWI指令地址),将其保存在R4寄存器中。通过BIC      R4, R4, #0xFF000000 指令将SWI指令高8位清除掉,只保留低24位立即数,取得SWI指令编码;

3--根据SWI指令做出相应操作

  根据24位立即数中的软中断号判断用户程序的请求操作。如果24位立即数为1,表示led_on系统调用产生的软中断,则在管理模式下调用对应的亮灯操作do_led_on。如果24位立即数为2,表示led_off系统调用产生的软中断,则调用灭灯操作do_led_on,根据ATPCS调用规则,R0~R3做为参数传递寄存器,在软中断处理中没有使用这4个寄存器,而是使用R4作为操作寄存器的

4--返回并恢复现场 

    执行完系统调用操作之后,返回到swi_return(在调用对应系统操作时,通过LDREQ    LR, =swi_return设置了返回地址),执行返回处理,通过LDMIA    SP!, {R0-R12, PC}^ 指令将用户寄存器数据恢复到R0~R12,将进入软中断处理时保存的返回地址LR的值恢复给PC,实现程序返回,同时还恢复了状态寄存器。切换回用户模式下程序中继续执行。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. ; 异常向量表开始  
  2. ; 0x00: 复位Reset异常  
  3.          b       Reset   
  4.   
  5.    
  6.   
  7. ; 0x04: 未定义异常(未处理)  
  8. HandleUndef  
  9.   
  10.            b       HandleUndef   
  11.   
  12.    
  13.   
  14. ; 0x08: 软件中断异常,跳往软件中断处理函数HandleSWI  
  15.   
  16.     b         HandleSWI  
  17.   
  18. … …  
  19.   
  20. ; 省略其它异常向量和对应处理  
  21. … …  
  22.   
  23. ;***********************************************************************  
  24.   
  25. ; 软中断处理  
  26. ;***********************************************************************   
  27.   
  28. IMPORT do_led_on  
  29.   
  30. IMPORT do_led_off  
  31.   
  32. HandleSWI  
  33.   
  34.          STMFD     SP!, {R0-R12,  LR}            ; 保存程序执行现场  
  35.          LDR R4, [LR, #-4]                                   ; LR - 4 为指令" swi xxx" 的地址,低24位是软件中断号  
  36.          BIC   R4, R4, #0xFF000000                    ; 取得ARM指令24位立即数  
  37.              
  38.   
  39.          CMP          R4, #1                                    ; 判断24位立即数,如果为1,调用do_led_on系统调用  
  40.          LDREQ     LR, =swi_return                    ; 软中断处理返回地址  
  41.          LDREQ     PC, = do_led_on                    ; 软中断号1对应系统调用处理  
  42.              
  43.   
  44.          CMP          R4, #2                                    ; 判断24位立即数,如果为2,调用do_led_off系统调用  
  45.          LDREQ     LR, =swi_return                    ; 软中断处理返回地址  
  46.          LDREQ     PC, = do_led_off                            ; 软中断号2对应系统调用处理  
  47.              
  48.   
  49.          MOVNE    R0, #-1                                   ; 没有该软中断号对应函数,出错返回-1  
  50.   
  51. swi_return   
  52.   
  53.          LDMIA     SP!, {R0-R12, PC}^             ; 中断返回, ^表示将spsr的值复制到cpsr  


其实讲到这,会产生一个疑问,什么时候需要我们从用户模式切换到管理模式?我们应该记得系统调用,就是用户态向内核态的切换。

三、系统调用

  操作系统的主要功能是为应用程序的运行创建良好的环境,保障每个程序都可以最大化利用硬件资源,防止非法程序破坏其它应用程序执行环境,为了达到这个目的,操作系统会将硬件的操作权限交给内核来管理,用户程序不能随意使用硬件,使用硬件(对硬件寄存器进行读写)时要先向操作系统发出请求,操作系统内核帮助用户程序实现其操作,也就是说用户程序不会直接操作硬件,而是提供给用户程序一些具备预定功能的内核函数,通过一组称为系统调用的(system call)的接口呈现给用户,系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,将处理结果返回给应用程序。

      这好比我们去银行取款,用户自己的银行帐户不可能随意操作,必须要有一个安全的操作流程和规范,银行里的布局通常被分成两部分,中间用透明玻璃分隔开,只留一个小窗口,面向用户的是用户服务区,工作人员所在区域为内部业务操作区,取款时,将银行卡或存折通过小窗口交给业务员,并且告诉他要取多少钱,具体取钱的操作你是不会直接接触的,业务员会将银行帐户里减掉取款金额,将现金给你。上述操作流程可以很好保护银行系统,银行系统的操作全部由业务员来实现,用户只能向业务员提出自己的服务请求。银行里的小窗口就类似与操作系统的系统调用接口,是将用户请求传递给内核的接口。   



  操作系统里将用户程序运行在用户模式下,并且为其分配可以使用内存空间,其它内存空间不能访问,内核态运行在特权模式下,对系统所有硬件进行统一管理和控制。从前面所学知识可以了解到,用户模式下没有权限进行模式切换,这也就意味着用户程序不可能直接通过切换模式去访问硬件寄存器,如果用户程序试图访问没有权限的硬件,会产生异常。这样用户程序被限制起来,如果用户程序想要使用硬件时怎么办呢?用户程序使用硬件时,必须调用操作系统提供的API接口才可以,而操作系统API接口通过软件中断方式切换到管理模式下,实现从用户模式下进入特权模式。

 At91rm9200处理器对应的linux2.4.19内核系统调用对应的软中断定义如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #if defined(__thumb__)                             //thumb模式  
  2. #define __syscall(name)                          \  
  3.     "push    {r7}\n\t"                           \  
  4.     "mov    r7, #" __sys1(__NR_##name) "\n\t"    \  
  5.     "swi    0\n\t"                               \  
  6.     "pop    {r7}"  
  7. #else                                              //arm模式  
  8. #define __syscall(name) "swi\t" __sys1(__NR_##name) "\n\t"  
  9. #endif  
  10.   
  11. #define __sys2(x) #x  
  12. #define __sys1(x) __sys2(x)  
  13. #define __NR_SYSCALL_BASE    0x900000               //此为OS_NUMBER << 20运算值  
  14. #define __NR_open            (__NR_SYSCALL_BASE+ 5) //0x900005   


 举一个例子来说:open系统调用,库函数最终会调用__syscall(open),宏展开之后为swi #__NR_open,,swi #0x900005触发中断,中断号0x900005存放在[lr,#-4]地址中,处理器跳转到arch/arm/kernel/entry-common.Svector_swi读取[lr,#-4]地址中的中断号,之后查询arch/arm/kernel/entry-common.S中的sys_call_table系统调用表,该表内容在arch/arm/kernel/calls.S中定义,__NR_open在表中对应的顺序号为

__syscall_start: 

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. .long    SYMBOL_NAME(sys_open)                     //第5个  
  2. ...  
  3. 将sys_call_table[5]中内容传给pc,系统进入sys_open函数,处理实质的open动作  
  4.   
  5. 注:用到的一些函数数据所在文件,如下所示  
  6. arch/arm/kernel/calls.S声明了系统调用函数  
  7. include/asm-arm/unistd.h定义了系统调用的调用号规则  
  8.   
  9. vector_swi定义在arch/arm/kernel/entry-common.S  
  10. vector_IRQ定义在arch/arm/kernel/entry-armv.S  
  11. vector_FIQ定义在arch/arm/kernel/entry-armv.S  
  12.   
  13. arch/arm/kernel/entry-common.S中对sys_call_table进行了定义:  
  14.     .type    sys_call_table, #object  
  15. ENTRY(sys_call_table)  
  16. #include "calls.S"                                 //将calls.S中的内容顺序链接到这里  

源程序:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. ENTRY(vector_swi)  
  2.     save_user_regs  
  3.     zero_fp  
  4.     get_scno                                        //将[lr,#-4]中的中断号转储到scno(r7)  
  5.     arm710_bug_check scno, ip  
  6. #ifdef CONFIG_ALIGNMENT_TRAP  
  7.     ldr    ip, __cr_alignment  
  8.     ldr    ip, [ip]  
  9.     mcr    p15, 0, ip, c1, c0                       @ update control register  
  10. #endif  
  11.     enable_irq ip  
  12.   
  13.     str    r4, [sp, #-S_OFF]!                       @ push fifth arg  
  14.   
  15.     get_current_task tsk  
  16.     ldr    ip, [tsk, #TSK_PTRACE]                   @ check for syscall tracing  
  17.     bic    scno, scno, #0xff000000                  @ mask off SWI op-code  
  18. //#define OS_NUMBER    9[entry-header.S]  
  19. //所以对于上面示例中open系统调用号scno=0x900005  
  20. //eor scno,scno,#0x900000  
  21. //之后scno=0x05  
  22.     eor    scno, scno, #OS_NUMBER << 20             @ check OS number  
  23. //sys_call_table项为calls.S的内容  
  24.     adr    tbl, sys_call_table                      @ load syscall table pointer  
  25.     tst    ip, #PT_TRACESYS                         @ are we tracing syscalls?  
  26.     bne    __sys_trace  
  27.   
  28.     adrsvc    al, lr, ret_fast_syscall              @ return address  
  29.     cmp    scno, #NR_syscalls                       @ check upper syscall limit  
  30. //执行sys_open函数  
  31.     ldrcc    pc, [tbl, scno, lsl #2]                @ call sys_* routine  
  32.     add    r1, sp, #S_OFF  
  33. 2:  mov    why, #0                                  @ no longer a real syscall  
  34.     cmp    scno, #ARMSWI_OFFSET  
  35.     eor    r0, scno, #OS_NUMBER << 20               @ put OS number back  
  36.     bcs    SYMBOL_NAME(arm_syscall)      
  37.     b    SYMBOL_NAME(sys_ni_syscall)                @ not private func  
  38.     /* 
  39.      * This is the really slow path. We're going to be doing 
  40.      * context switches, and waiting for our parent to respond. 
  41.      */  
  42. __sys_trace:  
  43.     add    r1, sp, #S_OFF  
  44.     mov    r0, #0                                   @ trace entry [IP = 0]  
  45.     bl    SYMBOL_NAME(syscall_trace)  
  46. /* 
  47. //2007-07-01 gliethttp [entry-header.S] 
  48. //Like adr, but force SVC mode (if required) 
  49.   .macro adrsvc, cond, reg, label 
  50.      adr\cond \reg, \label 
  51.   .endm 
  52. //对应反汇编: 
  53. //add lr, pc, #16 ; lr = __sys_trace_return 
  54. */  
  55.     adrsvc    al, lr, __sys_trace_return            @ return address  
  56.     add    r1, sp, #S_R0 + S_OFF                    @ pointer to regs  
  57.     cmp    scno, #NR_syscalls                       @ check upper syscall limit  
  58.     ldmccia    r1, {r0 - r3}                        @ have to reload r0 - r3  
  59.     ldrcc    pc, [tbl, scno, lsl #2]                @ call sys_* routine  
  60.     b    2b  
  61.   
  62. __sys_trace_return:  
  63.     str    r0, [sp, #S_R0 + S_OFF]!                 @ save returned r0  
  64.     mov    r1, sp  
  65.     mov    r0, #1                                   @ trace exit [IP = 1]  
  66.     bl    SYMBOL_NAME(syscall_trace)  
  67.     b    ret_disable_irq  
  68.   
  69.     .align    5  
  70. #ifdef CONFIG_ALIGNMENT_TRAP  
  71.     .type    __cr_alignment, #object  
  72. __cr_alignment:  
  73.     .word    SYMBOL_NAME(cr_alignment)  
  74. #endif  
  75.   
  76.     .type    sys_call_table, #object  
  77. ENTRY(sys_call_table)  
  78. #include "calls.S"  

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

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

相关文章

yum install 失败

https://blog.csdn.net/weixin_45621658/article/details/110734514 原因&#xff1a;centos6的默认源在2012年-12月左右被官方搞掉了 下列是错误详情 Bash [rootc8-20 ~]# yum makecache Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile YumRepo …

sphinx

2019独角兽企业重金招聘Python工程师标准>>> ./configure --prefix/usr/local/sphinx --with-mysql/usr/local/mysql make && make install cd /usr/local/sphinx/etc cp sphinx.conf.dist sphinx.conf vim sphinx.conf mysql -u test < /usr/local/sphi…

Exynos4412裸机开发综合练习

下面是一个案例需求&#xff1a; 1、编写一段程序,该程序的主要功能是监控电路板上的电压值,若电压值超过当前的电压限制则通过蜂鸣器报警,通过按键解除报警; 2、其具体要求如下; a) 程序下载20s后,进入电压采集状态(使用RTC ALARM功能完成), 要求1s采集1次电路板电压值;(采用…

Exynos4412 裸机开发 —— IIC总线

前言&#xff1a; I2C(Inter-Integrated Circuit)总线(也称 IIC 或 I2C) 是有PHILIPS公司开发的两线式串行总线&#xff0c;用于连接微控制器及外围设备&#xff0c;是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式&#xff0c;具有接口线少、控制方式…

Exynos4412裸机开发 —— A/D转换器

一、Exynos4412 A/D转换器概述 1、简述 10位或12位CMOS再循环式模拟数字转换器&#xff0c;它具有10通道输入&#xff0c;并可将模拟量转换至10位或12位二进制数。5Mhz A/D 转换时钟时&#xff0c;最大1Msps的转换速度。A/D转换具备片上采样保持功能&#xff0c;同时也支持待机…

Exynos4412裸机开发 —— UART

一、Exynos4412 UART 的特性 Exynos4412 中UART&#xff0c;有4 个独立的通道&#xff0c;每个通道都可以工作于中断模式或DMA 模式&#xff0c;即 UART 可以发出中断或 DMA 请求以便在UART 、CPU 间传输数据。UART 由波特率发生器、发送器、接收器和控制逻辑组成。 使用系统时…

Exynos4412裸机开发 —— 看门狗定时器

一、看门狗定时器概述 看门狗&#xff08;WatchDog Timer) 定时器和PWM的定时功能目的不一样。它的特点是&#xff0c;需要不同的接收信号&#xff08;一些外置看门狗芯片&#xff09;或重新设置计数器&#xff0c;保持计数值不为0。一旦一些时间接收不到信号&#xff0c;或计数…

Exynos4412裸机开发 —— RTC 实时时钟单元

RTC(Real-Time Clock) 实时时钟。RTC是集成电路&#xff0c;通常称为时钟芯片。在一个嵌入式系统中&#xff0c;通常采用RTC来提供可靠的系统时间&#xff0c;包括时分秒和年月日等&#xff0c;而且要求在系统处于关机状态下它也能正常工作&#xff08;通常采用后备电池供电&am…

Exynos4412裸机开发——中断处理

以KEY2控制LED3亮灭为例&#xff1a; 一、轮询方式 【0】检测按键k2&#xff0c;按键k2按下一次&#xff0c;灯LED2闪一次。 【1】查看原理图&#xff0c;连接引脚和控制逻辑 &#xff08;1&#xff09;按键k2 连接在GPX1_1引脚 &#xff08;2&#xff09;控制逻辑 k2 按…

远程WEB控制MP3播放器设计(基于mini2440)

网上有很多 基于mini2440的MP3播放器设计的资料&#xff0c;多是按键控制&#xff0c;这里博主做了些轻微改动&#xff0c;利用远程WEB来控制MP3播放&#xff0c;具体怎么实现&#xff0c;下面会给出&#xff0c;大家先看看效果&#xff1a; WEB界面&#xff1a; 后台运行&…

嵌入式数据库 SQLite 浅析

SQLite是一个非常轻量级自包含(lightweight and self-contained)的DBMS&#xff0c;它可移植性好&#xff0c;很容易使用&#xff0c;很小&#xff0c;高效而且可靠。SQLite嵌入到使用它的应用程序中&#xff0c;它们共用相同的进程空间&#xff0c;而不是单独的一个进程。从外…

socket 请求Web服务器过程

HTTP协议只是一个应用层协议&#xff0c;它底层是通过TCP进行传输数据的。因此&#xff0c;浏览器访问Web服务器的过程必须先有“连接建立”的发生。 而有人或许会问&#xff1a;众所周知&#xff0c;HTTP协议有两大特性&#xff0c;一个是“无连接”性&#xff0c;一个是“无状…

有些事情现在不做一辈子就都不会做了

这句话最近一直印在我的脑海里。这句话最早是在Casperkid的百度空间里面看见的&#xff0c;那时他生日。作为师傅的刺&#xff08;道哥&#xff09;送了他自己写的一本《白帽子讲WEB安全》给他&#xff0c;并在扉页上写着这句话。那时一看到这句话&#xff0c;仿佛有种触电的感…

HTTP 数据包头解析

一、连接至Web服务器 一个客户端应用&#xff08;如Web浏览器&#xff09;打开到Web服务器的HTTP端口的一个套接字&#xff08;缺省为80&#xff09;。 例如&#xff1a;http://www.myweb.com:8080/index.html 在Java中&#xff0c;这将等同于代码&#xff1a; [java] view pla…

Shell 脚本中如何使用make命令

最近开发的项目中需要编写Shell脚本对整个工程进行自动化编译&#xff0c;即在Shell脚本中使用make命令来进行编译&#xff0c;下面回顾一下Shell脚本中如何使用make命令&#xff09; 在开发一个系统时&#xff0c;一般是将一个系统分成几个模块&#xff0c;这样做提高了系统的…

Shell 脚本知识回顾 (六) —— Shell 函数

一、Shell函数&#xff1a;Shell函数返回值、删除函数、在终端调用函数 函数可以让我们将一个复杂功能划分成若干模块&#xff0c;让程序结构更加清晰&#xff0c;代码重复利用率更高。像其他编程语言一样&#xff0c;Shell 也支持函数。Shell 函数必须先定义后使用。 Shell 函…

Shell 脚本知识回顾 (五) —— Shell 循环

一、Shell for循环 与其他编程语言类似&#xff0c;Shell支持for循环。 for循环一般格式为&#xff1a;for 变量 in 列表 docommand1command2...commandN done 列表是一组值&#xff08;数字、字符串等&#xff09;组成的序列&#xff0c;每个值通过空格分隔。每循环一次&…

Shell 脚本知识回顾 (四) —— Shell 命令及Shell 相关语句

一、Shell echo命令 echo是Shell的一个内部指令&#xff0c;用于在屏幕上打印出指定的字符串。命令格式&#xff1a;echo arg您可以使用echo实现更复杂的输出格式控制。 显示转义字符 echo "\"It is a test\""结果将是&#xff1a;"It is a test"…

Shell 脚本知识回顾 (三) —— 替换、运算符、字符串、数组

一、Shell替换&#xff1a;Shell变量替换&#xff0c;命令替换&#xff0c;转义字符 如果表达式中包含特殊字符&#xff0c;Shell 将会进行替换。例如&#xff0c;在双引号中使用变量就是一种替换&#xff0c;转义字符也是一种替换。 举个例子&#xff1a; [cpp] view plaincop…

Shell 脚本知识回顾 (二) —— Shell变量

一、Shell变量&#xff1a;Shell变量的定义、删除变量、只读变量、变量类型 Shell支持自定义变量。定义变量 定义变量时&#xff0c;变量名不加美元符号&#xff08;$&#xff09;&#xff0c;如&#xff1a; [cpp] view plaincopy variableName"value" 注意&…