【Linux驱动】字符设备驱动程序框架 | LED驱动

news2025/1/18 20:25:57

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • 🏀Hello驱动程序
    • ⚽驱动程序框架
    • ⚽编程
  • 🏀LED驱动
    • ⚽配置GPIO
    • ⚽编程
      • 驱动程序
        • 映射虚拟地址
      • 应用层
  • 🏀总结

🏀Hello驱动程序

Linux下一切皆文件,使用open系统调用打开文件时会得到一个文件描述符,也被叫做文件句柄

图

如上图所示,在打开该文件进程的PCB中有一个文件描述符表的指针struct file_struct* files,该指针指向属于该进程的文件描述符表,本质上就是一个数组,所谓文件句柄就是该数组的下标,每打开一个文件,就在该数组中放入这个文件的struct file*指针,并且返回数组的下标。

struct file结构体的定义,在使用open时传入的flags、mode等参数都会被记录在这个结构体中,在读写文件时,文件的当前偏移地址也会保存在f_pos成员里。

  • 打开字符设备节点时,内核中也会打开一个对应的struct file结构体。

字符设备节点是一种特殊类型的文件,用于表示字符设备。这些设备通常以字符为单位进行数据的输入和输出,例如键盘或者串口。

  • 字符设备节点文件通常位于 /dev目录下。

图
如上图所示,当应用层使用open打开字符设备节点时,在内核中会创建一个struct file结构体,并且将传入的参数记录到该结构体中,而且会使用file_operations* f_op结构体成员中的open函数指针来打开设备节点。

当应用层使用read/write函数进行读写时,也会使用file_operations* f_op结构体成员中的read/write函数指针来实现读写目的,这个结构体是由字符设备驱动程序提供的。


图
如上如所示file_operations结构体的部分定义,其中有readwrite以及open等函数指针,当应用层使用相应的open/write/read系统调用接口时,最终会调用内核层中该结构体里对应的函数指针来实现目的。

⚽驱动程序框架

图
如上图所示,驱动程序的目的就是要在应用层调用open/write/read等系统调用接口时,在内核层中调用file_operations里的open/write/read函数指针,而函数指针指向的drv_open/drv_read/drv_write等驱动层函数是由我们自己定义的。在驱动程序中,实现硬件的初始化,以及数据读写。

  1. 定义自己的 file_operations 结构体。

前面本喵说过,file_operations结构体是由驱动程序提供的,而驱动程序又是我们写的,所以我们首先要做的就是定义自己的file_operations 结构体。

图
如上图所示,在hello_drv.c源文件中定义file_operations结构体变量,并且进行初始化,给函数指针赋值相应的函数。

  • owner:是一个指向模块所有者的指针,是必须设置的。
  • gcc编译器中增加了使用.结构体成员 = xxx来给成员变量赋值的语法。
  1. 实现对应的drv_open/drv_read/drv_write等函数。

图
如上图所示驱动函数的定义,由于现在讲解的是框架,所以本喵在函数里没有写任何操作,只是使用printk打印一些调试信息。

  • 内核中打印调试信息只能使用printk,不能使用printf
  • 使用命令行指令dmesg就能看到日志中的调试信息。
  1. 确定主设备号,也可以让内核自己分配。

每一个字符设备都有一个主设备号,用于标识设备的类型或者设备驱动程序。不同类型的设备或不同的驱动程序会有不同的主设备号。例如,所有的串口设备可能共享一个主设备号,而所有的打印机设备可能又共享另一个不同的主设备号。

  • 主设备号就像是一个类,可以用这个类定义出多个实例。
  • 主设备号可以由我们自己决定,也可以将其设置为0,让内核自己分配。

建议让内核去分配主设备号,因为我们并不是很清楚有哪些主设备号,自己决定的是否已经被使用。

/* 确定主设备号 */
static int major = 0

定义一个全局变量major来表示主设备号,暂时先给它赋值为0。

  1. file_operations 结构体注册到内核。

暂时可以认为在内核中有一个chardevs[]数组,该数组中存放的是字符设备节点的主设备号,当使用某一类字符设备时,会从该数组中寻找对应设备的file_operations结构体对象。

所谓注册就是将我们自己的字符设备主设备号注册到这个数组中,使用register_chrdev函数来实现:

major = register_chrdev(0, "hello", &hello_drv); 
  • 第一个参数是主设备号,如果传入的是0,则返回内核自动分配的主设备号。
  • 第二个参数是字符设备的名称,是一个字符串。
  • 第三个参数是我们提供的file_operations结构体指针。

调用该函数后,主设备号和file_operations以及设备名称就绑定在了一起,而且主设备号放入到了chardevs[]数组中。

  1. 定义入口函数,安装驱动程序时,就会去调用这个入口函数 。

file_operations注册到内核中是由入口函数完成的,入口函数使用宏__init修饰:

图

如上图所示,在安装驱动程序的时候,内核会自动去调用这个hello_init函数,在该函数中完成:

  • 注册file_operations结构体到内核中,并得到主设备号。

  • 创建设备信息类,使用class_create实现,该类中包含内核需要的设备节点信息,更方便内核去创建节点。

  • 创建设备节点,使用device_create实现,此时在内核中会生成一个/dev/hello路径用来表示节点设备。

  1. 出口函数,卸载驱动程序时,就会去调用这个出口函数。

有入口函数就有出口函数,出口函数使用宏__exit来修饰:

图

如上图所示函数,在卸载时会由内核自动调用,都要卸载了,就要将前面注册到内核中的字符设备移除,使用unregister_chrdev实现,并且将前面创建的字符设备类和设备节点都销毁,使用class_destroydevice_destroy实现。

  1. 完善设备信息
module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

使用module_init告诉内核hello_init函数是入口函数,使用module_exit告诉内核hello_exit是出口函数。

使用MODULE_LICENSE表明遵循GPL协议,否则是无法使用我们的驱动程序的。


至此已经实现了一个驱动程序框架,在命名上以hello为例,这个可以作为一个模板,在使用的时候只需要将hello改为相应的设备名字即可,然后再在我们自己实现的驱动函数中增加一些具体的代码。

⚽编程

下面用上面的框架来实现一个不涉及硬件操作的hello驱动程序:

  • 命令行输入./hello_drv_test -w abc,将abc字符串写入内核缓冲区中。
  • 命令行输入./hello_drv_test -r,从内核缓冲区中读出刚刚输入的字符串。

驱动层代码:

图
如上图所示代码,只需要实现file_operations结构体中的readwrite方法,也就是对应的hello_drv_readhello_drv_wite函数,其他的没有用到。

  • 使用__user修饰的buf,表示这是来自用户层的缓冲区。

用户层的缓冲区不能使用strcpy等应用层函数直接操作,而是必须要使用内核提供的复制函数:

  • copy_to_user:从内核缓冲区复制数据到用户缓冲区,第一个参数是目的buf,第二个参数是源kernel_buffer,第三个参数是要复制的字节数。
  • copy_from_user:从用户缓冲区复制数据到内核缓冲区,参数参考上面。

由于定义的缓冲区大小是1024,防止越界,使用宏MIN将1024和用户层指定的数据大小size作比较,取较小值作为复制数据的大小。

应用层代码:

tu
如上图所示,使用命令行参数传入-w-r,以及要写入的字符串,在main函数中:

  • 先打开/dev/hello目录下的字符设备节点,在应用层看来,这就是一个普通文件。
  • 根据命令行中的第二个参数判断:
    • -w:使用write将第三个参数的字符串写入到内核缓冲区中。
    • -r:使用read将内核缓冲区的数据读出来。

这里应用层的wite最终会调用驱动层中的hello_drv_write,应用层的read最终会调用驱动层中的hello_drv_read

交叉编译:

图

如上图所示,在命令行中输入上面的三条指令,设置环境变量,从而实现交叉编译环境的配置。

图
如上图所示,使用该Makefile文件来编译驱动文件hello_drv.c和应用层测试文件hello_drv_test.c,暂时不用这些指令是什么意思,直接用就星。

tu

如上图所示,生成hello_drv.kohello_drv_test两个文件:

  • .ko后缀:表示这是一个内核模块,用于在运行时向内核动态添加功能,而不需要重新编译整个内核。

挂载根文件系统:
图
如上图,将生成的hello_drv.ko驱动模块文件和hello_drv_test测试可执行程序复制到nfs_rootfs文件下。

图
如上图所示,在IMX6ULL开发板上,通过串口工具执行mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt指令,将刚刚进行编译等操作的Linux服务器里的根文件系统挂载到开发版上。

  • nfs_rootfs是一个通过网络文件系统(NFS)挂载的根文件系统。
  • 网络文件系统(NFS):NFS 是一个分布式文件系统协议,它允许用户在网络上访问存储在远程计算机上的文件,就像访问本地存储的文件一样。
  • 根文件系统(rootfs):根文件系统包含操作系统的核心组件,如可执行文件、库文件、配置文件等。

此时在开发板上就相当于有了一个Linux操作系统,实际上用的是服务器的系统,可以看到,服务器的根文件系统中有什么,挂载之后的/mnt里就有什么。

安装驱动程序:
图
如上图所示,进入开发板挂载的根文件/mnt中,找到我们的hello_drv.ko所在位置,然后执行insmod hello_drv.ko指令安装设备节点的驱动程序,安装完毕后,在/dev设备节点中可以看到hello设备节点。

测试:

tu
如上图所示,执行应用层测试程序hello_drv_test

  • 在执行可执行程序的命令行参数中使用-w选项,写入A-Big-MiaoMi字符串到内核缓冲区中。
  • 再使用-r选项,从内核缓冲区中读取刚刚写入数据,结果是APP read: A-Big-MiaoMi

根据上面测试结果,说明我们的第一个驱动程序就写成功了。

🏀LED驱动

⚽配置GPIO

配置GPIO通用步骤:
tu
如上图所示IMX6ULLGPIO框图,输出功能的配置和其他芯片一样分为四步:

  • 使能GPIO组:设置CCM寄存器组中的CCGRx寄存器中的相应位CGx来使能对应的GPIO组。
  • 选择GOIO为通用输入输出功能:设置IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPERx寄存器中的MUX_MODE位将IO口设为通用输入输出功能。
  • 选择方向:设置GPIOx_GDIR中的相应位,0表示输入,1表示输出。
  • 写数据寄存器:设置GPIOx_DR中的相应位,0表示输出低电平,1表示输出高电平。

具体单板:

tu
如上图所示本喵的IMX6ULL开饭上LED2的电路图:

  • GPIO5_3输出低电平,LED灯亮。
  • GPIO5_3输出低电平,LED灯灭。

按照上面的配置步骤,寻找GPIO5_3的那几个寄存器和对应的比特位:

  1. CCM_CCGR1中的CG15

图
如上图所示,CCM_CCGR1中的CG15是保留的,在IMX6ULL中,GPIO5这组GPIO默认使能,所以不用设置。CCM_CCGR1的绝对地址是0x020C4000 + 0x6C = 0x020C406C

  1. IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3

图
如上图所示,将 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3寄存器中的MUX_MODE4位配置为101,选择GPIO5_IO03为通用输入输出模式。该寄存器的绝对地址是0x02290000 + 0x14 = 0x02290014

  1. GPIO5_GDIR

图
如上图,使用的是GPIO5_3IO口,所以要配置GPIO5_GDIR中的bit3,该位为1,表示输出,该位为0,表示输入。该寄存器的偏移量是0x4

图
如上图所示,GPIO5的基地址是0x020AC000,所以GPIO5_GDIR的绝对地址是0x020AC000 + 0x4 = 0x020AC004

  1. GPIO5_DR

图
如上图所示,GPIO5_DR中的bit3设置为1,GPIO5_3就输出高电平,设置为0就输出低电平,该寄存器的地址偏移量是0x0,所以它的绝对地址就是0x020AC000 + 0x0 = 0x020AC000

⚽编程

驱动程序

按照驱动程序框架来编写:

1. 提供file_operations并实现相应驱动函数:

tu
如上图所示file_operations结构体,只初始化三个成员:

  • owner是必须有的,其值是该模块所属者的指针THIS_MODULE

  • open初始化为led_open
    图
    如上图所示,在led_open函数中,对GPIO5_3进行使能,功能选择以及方向选择,当应用层调用open系统调用时,最终会调用驱动层的led_open函数,对GPIO进行初始化。

  • write初始化为led_write

图
如山图所示,在led_write函数中,使用copy_from_user读取应用层调用write系统调用时写入的参数,并且复制到val中,根据该参数的逻辑值来控制LED灯的状态:

  • 用户层写入非0值:向GPIO5_DR寄存器的bit3写0,LED灯亮。
  • 用户层写入0值:向GPIO5_DR寄存器的bit3写1,LED灯灭。

2. 实现入口函数并注册设备节点:

图
如上图所示,创建相应寄存器的指针变量,然后在入口函数中首先使用resister_chrdev注册设备节点,然后再使用ioremap函数映射虚拟地址。然后再使用class_createdevice_create为内核创建设备节点提供信息。

  • 用来指向寄存器的指针使用volatile关键字修饰,保持内存可见性。
  • 对于寄存器来说,有没有数据写入区别非常大,所以要保证每次操作寄存器都能写入,不被优化。
映射虚拟地址

前面查芯片手册时看到的寄存器地址是实实在在的物理地址,但是在Linux中是不允许直接操作物理地址的。

led_openled_write中操作寄存器时使用的指针IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3GPIO5_GDIR,以及GPIO5_DR变量,其中的地址都是经过映射以后得到的虚拟地址。

TU

如上图所示,在Linux系统中存在多个进程,假设此时存在两个进程,每个进程都有一个PCB结构体,里面的struct mm_struct* mm指向各自进程地址空间(也叫虚拟地址空间)。

  • 不同进程的进程地址空间是相互独立的,互不影响。
  • 每个进程地址空间都包含栈区,共享区,堆区,数据段,代码段等等区域。

led_openled_write驱动函数中使用的寄存器指针,它们属于全局变量,所以存放在使用该驱动程序进程地址空间的数据段

如果这两个进程都会调用openwrite系统调用来操作LED灯:

  • 假设进程地址空间的数据段存放的是GPIO5相关寄存器的物理地址

进程1对GPIO5_3IO口的操作是正常的,符合规范的,但是进程2对GPIO5_3IO口的操作是违规的,如越界操作,溢出等错误操作。

由于进程1和进程2操作的是物理地址,所以进程2的错误操作会影响到进程1的正常操作,两个进程就相互影响了。

  • 进程地址空间的数据段存放的是GPIO5相关寄存器的映射后的虚拟地址

实际上采样的就是这种方式,使用虚拟地址的方式来管理和保护内存。上图中的MMU可以把物理地址和虚拟地址建立映射关系,当操作进程地址空间中的虚拟地址时:

  • MMU会先判断该操作是否合法,对物理地址形成保护,防止非法访问。
  • 操作合法时,去该虚拟地址所映射的物理地址处进行操作。

此时进程1和进程2就不会互相影响,当进程2对寄存器进程非法操作时,MMU就会直接驳回它的操作请求。

  • 操作系统Linux运行在保护模式下,使用虚拟内存来管理和保护内存,同一个物理地址可以被映射到不同进程的不同虚拟地址上。
  • 直接访问物理地址会绕过这层保护,可能导致系统不稳定或不安全。

使用ioremap进行虚拟地址映射时:

  • 第一个参数:要进行映射的物理地址。
  • 第二个参数:要映射的内存大小(字节)。

由于IMX6ULLGPIO5_3涉及到的寄存器都是32位的,也就是四个字节,所以第二个参数都是4,将使用ioremap映射后的3个虚拟地址赋值给那几个寄存器指针全局变量。

  • 虽然映射的大小是4个字节,但是映射时是以 页(4KB) 为单位的,所以真正映射出来的虚拟地址大小是4KB。

3. 实现出口函数和完善驱动信息:

图
如上图所示,在出口函数中,首先要把映射的虚拟地址销毁掉,使用iounmap函数实现,只有一个参数就是映射后得到的虚拟地址。

然后就是按照驱动框架中的操作,将设备类以及设备节点全部销毁,以及销毁设备节点的注册,最后再告诉内核入口函数和出口函数,以及声明一下使用GPL开源协议。

应用层

图
如上图所示应用层的测试代码led_drv_test.c,在执行测试程序时,命令行中输入的指令有两种:

  • led_drv_test /dev/myled on:表示点亮LED灯。
  • led_drv_test /dev/myled off:表示熄灭LED灯。

main函数中,首先判断命令行参数的个数,如果个数不为3,说明使用错误,则提示用法并直接返回-1。

参数正确以后,先打开/dev/myled目录下的设备节点,可以看到,使用的是open系统调用,应用层只把它当作一个普通文件,并不知道这是一个字符设备。

根据命令行参数中的最后一个进行判断:

  • 如果是“on”:则将后面要写入内核中的数据status修改为1。
  • 如果是"off":则不做任何修改,status使用创建时的初始值0。

最后使用write系统调用,将status这一个字节的数据写入到内核,驱动函数根据这个数据的逻辑值来判断是点亮LED灯还是熄灭LED灯。


交叉编译和前面hello驱动程序一样,只是需要对Makefile文件稍作修改:

tu

最后在IMX6ULL上挂载的跟文件系统中使用insmod led_drv.ko按照myled设备节点,然后执行led_drv_test测试程序就可以点亮和熄灭LED灯了,这里本喵就不贴板子的效果图了。

🏀总结

通过和硬件无关的hello驱动程序来引出驱动程序的框架。然后使用该框架实现了IMX6ULL单板上LED灯的驱动程序。

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

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

相关文章

NHNL因子如何刻画行业强弱

根据华福证券-市场情绪指标专题(五),进行了提炼和改写,特此致谢! ( N H N L ) % ( c o u n t ( H H V ) − c o u n t ( L L V ) ) / N (NHNL)\% (count(HHV) - count(LLV))/N (NHNL)%(count(HHV)−count(LLV))/N 个…

Uniapp + Vue3 + Pinia + Vant3 框架搭建

现在越来越多项目都偏向于Vue3开发&#xff0c;想着uniapp搭配Vue3试试效果怎么样&#xff0c;接下来就是详细操作步骤。 初始化Uniapp Vue3项目 App.vue setup语法 <script setup>import {onLaunch,onShow,onHide} from dcloudio/uni-apponLaunch(() > {console.l…

Linux poll 和 select 机制

poll select 介绍 使用非阻塞 I/O 的应用程序常常使用 poll, select, 和 epoll 系统调用. poll, select 和 epoll 本质上有相同的功能: 每个允许一个进程来决定它是否可读或者写一个 或多个文件而不阻塞. 这些调用也可阻塞进程直到任何一个给定集合的文件描述符可用来 读或写.…

叮咚,微信年度聊天报告(圣诞节版)请查收~丨GitHub star 16.8k+

微信年度聊天报告——圣诞节特别版&#xff0c;快发给心仪的ta吧~ 开源地址 GitHub开源地址&#xff1a;https://github.com/LC044/WeChatMsg 我深信有意义的不是微信&#xff0c;而是隐藏在对话框背后的一个个深刻故事。未来&#xff0c;每个人都能拥有AI的陪伴&#xff0c;…

「Vue3面试系列」Vue3.0性能提升主要是通过哪几方面体现的?

文章目录 一、编译阶段diff算法优化静态提升事件监听缓存SSR优化 二、源码体积三、响应式系统参考文献 一、编译阶段 回顾Vue2&#xff0c;我们知道每个组件实例都对应一个 watcher 实例&#xff0c;它会在组件渲染的过程中把用到的数据property记录为依赖&#xff0c;当依赖发…

显卡之争!英伟达和AMD下场互掐!GPU霸主地位是否能保?

大家好&#xff0c;我是二狗。 英伟达和AMD这两家芯片巨头掐起来啦&#xff01; 事情的起因是&#xff0c;两周前AMD董事会主席兼CEO苏姿丰在一场活动中发布了用于生成式AI和数据中心的新一代Intinct MI300X GPU芯片加速卡。 单单发布显卡没啥问题&#xff0c;但是AMD声称MI300…

【Spring实战】03 JDBC常用操作

文章目录 1. JdbcTemplate 类1&#xff09;queryForList2&#xff09;update3&#xff09;query4&#xff09;execute5&#xff09;queryForObject 2.代码及执行1&#xff09;代码2&#xff09;执行 3. 优点4. 详细代码总结 Spring JDBC 是 Spring 框架提供的一种用于简化数据库…

05. Springboot admin集成Actuator(一)

目录 1、前言 2、Actuator监控端点 2.1、健康检查 2.2、信息端点 2.3、环境信息 2.4、度量指标 2.5、日志文件查看 2.6、追踪信息 2.7、Beans信息 2.8、Mappings信息 3、快速使用 2.1、添加依赖 2.2、添加配置文件 2.3、启动程序 4、自定义端点Endpoint 5、自定…

基于epoll的web服务器(C语言版本)

基于epoll的web服务器(C语言版本) 1. 初始化监听套接字 包括创建监听套接字&#xff0c;设置端口复用&#xff0c;绑定&#xff0c;设置监听等步骤 1.1 创建监听套接字&#xff08;socket函数&#xff09; socket()打开一个网络通讯端口&#xff0c;如果成功的话&#xff0…

界面控件DevExpress v23.2全新发布 - 官宣正式支持.NET 8

DevExpress拥有.NET开发需要的所有平台控件&#xff0c;包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。屡获大奖的软件开发平台DevExpress 今年第一个重要版本v23.1正式发布&#xff0c;该版本拥有众多…

【精选】vulnhub CTF6 linux udev提权 (青铜门笔记)

一、信息收集 1.主机探测 发现靶机的IP地址是&#xff1a;192.168.103.130 ┌──(root&#x1f480;kali)-[~] └─# arp-scan -l2.访问web页面 发现有个登录的页面&#xff0c;尝试了弱口令&#xff0c;但是发现没有成功&#xff1b; 所以&#xff0c;我们需要在后面的信…

单词接龙[中等]

一、题目 字典wordList中从单词beginWord和endWord的 转换序列 是一个按下述规格形成的序列beginWord -> s1 -> s2 -> ... -> sk&#xff1a; 1、每一对相邻的单词只差一个字母。 2、对于1 < i < k时&#xff0c;每个si都在wordList中。注意&#xff0c;beg…

数值分析期末复习

第一章 科学计算 误差 解题步骤 先求绝对误差: ∣ x − x ∗ ∣ |x - x^*| ∣x−x∗∣求相对误差限: ∣ x − x ∗ ∣ x ∗ \frac{|x\,\,-\,\,x^*|}{x^*} x∗∣x−x∗∣​求有效数字 ∣ x − x ∗ ∣ 需要小于它自身的半个单位 |x-x^*|\text{需要小于它自身的半个单位} ∣…

Kafka集群架构原理(待完善)

kafka在zookeeper数据结构 controller选举 客户端同时往zookeeper写入, 第一个写入成功(临时节点), 成为leader, 当leader挂掉, 临时节点被移除, 监听机制监听下线,重新竞争leader, 客户端也能监听最新leader leader partition自平衡 leader不均匀时, 造成某个节点压力过大, …

数字信号的理解

1 数字信号处理简介 数字信号处理 digital signal processing&#xff08;DSP&#xff09;经常与实际的数字系统相混淆。这两个术语都暗示了不同的概念。数字信号处理在本质上比实际的数字系统稍微抽象一些。数字系统是涉及的硬件、二进制代码或数字域。这两个术语之间的普遍混…

物联网产品设计,聊聊设备OTA的升级

物联网产品设计部分的OTA设备固件是一个非常重要的部分&#xff0c;能够实现升级用户服务、保障系统安全等功能。 在迅速变化和发展的物联网市场&#xff0c;新的产品需求不断涌现&#xff0c;因此对于智能硬件设备的更新需求就变得空前高涨&#xff0c;设备不再像传统设备一样…

SQL分类

SQL分类 DDL 查询库 查询表 创建表 修改表 DML 添加数据 修改数据 删除数据 DQL 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 执行顺序 DCL 管理用户 管理权限 数据类型 数值类型 字符串类型 日期类型

从零构建tomcat环境

一、官网构建 1.1 下载 一般来说对于开源软件都有自己的官方网站&#xff0c;并且会附上使用文档以及一些特性和二次构建的方法&#xff0c;那么我们首先的话需要从官网或者tomcat上下载到我们需要的源码包。下载地址&#xff1a;官网、Github。 这里需要声明一下&#xff…

龙芯loongarch64服务器编译安装tensorflow-io-gcs-filesystem

前言 安装TensorFlow的时候,会出现有些包找不到的情况,直接使用pip命令也无法安装,比如tensorflow-io-gcs-filesystem,安装的时候就会报错: 这个包需要自行编译,官方介绍有限,这里我讲解下 编译 准备 拉取源码:https://github.com/tensorflow/io.git 文章中…

80x86汇编—汇编程序基本框架

文章目录 First Program指令系统伪指令数值表达式 程序框架解释int 21 中断 通过一个基本框架解释各个指令和用处&#xff0c;方便复习。所以我认为最好的学习顺序就是先看一段完整的汇编代码程序&#xff0c;然后给你逐个逐个的解释每一个代码是干嘛用的。然后剩下的还有很多指…