【Linux驱动】休眠与唤醒 | POLL机制 | 异步通知 | 阻塞与非阻塞 | 软件定时器

news2024/11/16 9:18:07

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

目录

  • 🏓休眠与唤醒
    • 🏸内核函数
    • 🏸驱动框架及编程
  • 🏓POLL机制
    • 🏸驱动编程
    • 🏸应用编程
  • 🏓异步通知
    • 🏸驱动编程
    • 🏸应用编程
  • 🏓阻塞与非阻塞
    • 🏸应用编程
    • 🏸驱动编程
  • 🏓定时器
    • 🏸定时器消抖
  • 🏓总结

🏓休眠与唤醒

在前面讲解按键驱动时,本喵提到过休眠唤醒的按键驱动方式:应用程序必须等待按键按下后,才会继续执行,否则则处于阻塞状态。

图
如上图所示应用层APP获取按键值的流程:

  • APP调用read系统调用读取按键数据。
  • APP进入内核态,通过文件系统中的sys_read调用驱动层的drv_read
  • 发现有数据则复制到用户空间并马上返回,如果没有数据则APP休眠,也就是阻塞不动。

驱动中没有数据时,APP在内核态执行到drv_read时会休眠。所谓休眠就是把自己的状态改为非RUNNING,这样内核的调度器就不会让该APP运行了。

图

如上图,当APP1因为没有按键数据而休眠时,内核会挑选出其他APP去运行,当按下按键后:

  • 驱动程序中的中断服务程序被调用,它会记录数据,并唤醒APP1。
  • 当APP1再次运行时,就会继续执行drv_read中剩下的代码,把数据复制回用户空间,然后返回用户空间。

所谓唤醒就是把APP的状态改为RUNNING,这样内核的调度器在合适的时间就会让它运行。

APP1的执行过程如红色实线所示,它被分成了两段:

  • 红线所涉及的代码,都是APP1调用的,它都属于APP1的上下文
  • 按键的中断服务程序,不属于APP1的上下文,它属于中断的上下文。

🏸内核函数

休眠函数:

当按键的驱动程序drv_read中没有按键数据时,就需要我们在驱动程序中让当前进程/线程去休眠,此时就需要使用内核提供的一些休眠函数:

函数说明
wait_event_interruptible(wq, condition)休眠,直到condition为真,休眠期间是可以被信号打断的
wait_event(wq, condition)休眠,直到condition为真,退出的唯一条件就是condition为真,信号也无法打断。
wait_event_interruptible_timeout(wq, condition, timeout)休眠,直到condition为真或者超时,休眠期间可以被信号打断
wait_event_timeout(wq, condition, timeout)休眠,直到condition为真或者超时,信号无法打断。

参数说明:

  • wq:waitqueue,等待队列。
    • 休眠时除了把程序状态改为非RUNNING之外,还要把进程/进程放入wq中,以后中断服务程序要从wq中把它取出来唤醒。
  • condition:可以是变量,也可以是任何表达式,表示一直等待,直到condition为真

唤醒函数:

在按键的中断服务函数中,需要去唤醒处于休眠状态的进程/线程,同样也通过内核提供的唤醒函数:

函数说明
wake_up_interruptible(x)唤醒 x 队列中状态为TASK_INTERRUPTIBLE的线程,只唤 醒其中的一个线程
wake_up_interruptible_nr(x, nr)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,只唤 醒其中的 nr 个线程
wake_up_interruptible_all(x)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,唤醒 其中的所有线程
wake_up(x)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE ”或 “TASK_UNINTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_nr(x, nr)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中nr个线程
wake_up_all(x)唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,唤醒其中的所有线程

参数说明:

  • x:就是前面在休眠时存放非RUNNING状态的线程/进程的队列。
  • TASK_INTERRUPTIBLE:该状态的休眠线程是可以被信号中断休眠的。
  • TASK_UNINTERRUPTIBLE:该状态的休眠线程是不可以被信号中断休眠的。

🏸驱动框架及编程

图
如上图所示是休眠唤醒的驱动框架,根据该框架,我们需要做这几件事情:

  • 初始化wq队列,使用DECLARE_WAIT_QUEUE_HEAD()宏函数实现。
  • 在驱动层的drv_read中,调用wait_event_interruptible()函数:
    • 它本身会判断event参数是否为FALSE,如果为FASLE表示无数据,会将自己放入wq等待队列并休眠。
    • 当从wait_event_interruptible()函数返回后,会继续把数据复制回用户空间。
  • 在中断服务程序中,设置eventTRUE,并且调用wake_up_interruptible唤醒正在队列中休眠的线程/进程。

编程:

下面是在之前本喵实现的按键中断程序的基础上实现休眠唤醒,设备树文件本喵就不再贴图了,有兴趣的小伙伴移步这里。

注册platform_driver结构体对象以及初始化proberemove等内容和前面的按键中断完全一样,本喵不再讲解。

本喵这里为了防止按键数据丢失,使用环形缓冲区来记录按键数据:

图
如上图所示环形缓冲区以及操作的接口函数定义,包括判空,判满,写数据和读数据。

  • gpio_key_drv_read函数

当应用层使用系统调用read时,最终会调用到file_operations结构体中的read函数,也就是gpio_key_drv_read驱动层读取数据的函数。

图
如上图代码所示,先使用DECLARE_WAIT_QUEUE_HEAD宏函数初始化等待队列gpio_key_wait

当调用到驱动层的gpio_key_drv_read函数后,使用wait_event_interruptible函数:

  • 如果环形缓冲区是空的,说明没有数据,则将当前线程放入到等待队列中休眠。
  • 如果环形缓冲区不是空的,说明有数据,则不将当前线程放入等待队列,而是直接调用获取环形缓冲区中的数据并且拷贝到用户空间中。

如果因为没有数据而处于休眠状态,则该线程就会阻塞到这里,当该线程被唤醒后,说明环形缓冲区中有数据了,则读取数据并且拷贝到用户空间中。

  • gpio_key_isr中断服务函数

如果线程因为没有按键数据而休眠了,那么只能在中断服务程序中将其唤醒:

图
如上图所示中断服务函数,当按键中断产生后,获取GPIO引脚的逻辑值,然后构造出按键值:

  • 按键值key:低八位是GPIO引脚的逻辑值,其余位是描述引脚信息的整数。

再调用wake_up_interruptible从等待队列gpio_key_wait中唤醒因为没有读取到按键数据而休眠的线程。

  • 在中断服务函数中向环形缓冲区中写入了按键数据,也就是意味着驱动程序drv_read中的event:!is_key_buf_empty()变成了TRUE,所以线程就被唤醒了。
  • 由于gpio_key_wait是专门用来存放读取按键的线程的,所以目前该等待队列中只有一个线程,中断服务函数不会唤醒错误。

演示:

tu
如上图应用层的button_test.c测试代码,在打开按键设备以后,在while死循环中使用read系统调用读取按键数据,如果读取到就打印出来:

  • 如果没有按键数据,该进程会阻塞在read调用处,不会执行下去。
  • 当有按键数据时,进程就会继续执行。

将驱动程序和测试程序上传到服务器进行编译,将得到的gpio_key_drv.kobutton_test拷贝到网络文件系统中供开发板使用,本喵这里就不演示具体的步骤了。

图

如上图所示,将按键驱动程序安装以后,再运行应用层的测试程序:

  • 运行起来后,程序阻塞在read处,因为没有按键值。
  • 按下开发板上的按键后,驱动程序打印出了按键的整数编号和逻辑值。
  • 应用程序打印出了我们在驱动程序中构建后的按键值。

🏓POLL机制

休眠唤醒机制的致命缺陷在于,如果没有按键数据,进程/线程就会一直死等下去。而POLL机制就是为了解决这个问题的:

  • APP不知道驱动程序中是否有数据,可以先调用poll函数查询一下,poll函数可以传入超时时间。
  • APP进入内核态,调用到驱动程序的drv_poll函数,如果有数据的话立刻返回。
  • 如果发现没有数据时就休眠一段时间
    • 当按下按键时,驱动程序的中断服务程序被调用,它会记录数据并且唤醒APP。
    • 当超时时间到了之后,内核也会唤醒APP。
  • APP根据poll函数的返回值就可以知道是否有数据,如果有数据就调用read得到数据。

tu
如上图所示poll机制执行的流程,分为9步:

  1. APP使用open系统调用打开按键设备。
  2. 通过内核文件系统中sys_open函数调用file_operation结构体中的drv_open驱动函数来打开设备。

每一个设备,内核都会将其看成是一个文件,都会在内核中创建一个struct file结构体来描述这个设备,该结构体就位于内核的文件系统中。

  1. APP调用poll系统调用后进入内核态。

  2. 内核文件系统中的sys_poll,会在死循环for中,先调用驱动程序的drv_poll来获取状态event

    • 在驱动程序drv_poll中,要把当前线程挂入到等待队列wq中,否则在唤醒的时候就找不到该线程了。
    • 但是驱动程序drv_poll不会让当前线程休眠
  3. 返回的状态表示当前没有数据,那么内核文件系统就让该线程休眠一会儿。

  4. 线程休眠过程中,按下了按键,产生了按键中断,在中断服务函数中记录按键值,并且从wq等待队列中将线程唤醒。

  5. 线程被唤醒后处于内核文件系统中的for死循环中,所以还要再执行一次drv_poll驱动程序,获取按键数据的状态。

  6. 此时获取到的数据状态表示有按键数据,就会从返回到用户态,APP可以继续执行不再阻塞。

  7. APP根据poll的返回值发现有按键数据,则调用read函数读取按键数据。

如果一直没有按键数据,也就是线程在休眠后一直没有被唤醒,此时的流程也是类似的,从第三步开始看:

  1. APP调用poll系统调用后进入内核态。
  2. 导致驱动程序的drv_poll被调用。
  3. 假设当前没有数据,则休眠一会。
  4. 在休眠过程中,一直没有按下了按键,超时时间到了,内核把这个线程唤醒。
  5. 线程从休眠中被唤醒,继续执行for循环,再次调用drv_poll驱动程序获取数据状态。
  6. 此时获取到的数据状态仍然表示没有数据,但是超时时间已经到了,也只能从内核态返回用户态了。
  7. APP根据poll的返回值发现没有按键数据,则不能调用read函数读取按键数据。

这个过程中有几点需要注意:

  • drv_poll要把线程挂入队列wq,但是并不是在drv_poll中进入休眠,而 是在调用drv_poll之后休眠。

drv_poll驱动程序只做两件事情:

  • 把线程放入到等待队列wq中,但是不休眠。
  • 返回event事件状态,而不是返回事件值。
  • APP调用一次poll,有可能会导致drv_poll被调用两次:
    • 进入内核文件系统的for循环中先调用一次获取状态。
    • 被唤醒后(中断或者超时)执行for循环再调用一次,判断是数据到来还是超时,然后返回用户态。
  • APP要判断poll返回的原因:
    • 有数据到来,还是超时。有数据到来才能调用read函数读取按键值。

🏸驱动编程

使用poll机制时,驱动程序的核心就是提供对应的drv_poll函数。在drv_poll函数中要做两件事:

  • 把当前线程挂入等待队列wq,使用内核提供的函数poll_wait来实现:
    • APP调用一次poll,可能导致drv_poll被调用2次,但是我们并不需要把当前线程挂入队列2次。
    • 使用内核的函数poll_wait把线程挂入队列,如果线程已经在队列里了,它就不会再次挂入。
  • 返回设备状态,APP调用poll函数时,有可能是查询有没有数据可以读,有可能是查询有没有空间可以写数据
    • 有数据可以读:(POLLIN | POLLRDNORM),POLLRDNORM 等同于 POLLIN,为了兼容某些 APP 把它们一起返回。
    • 有空间可以写:(POLLOUT | POLLWRNORM),POLLWRNORM 等同于 POLLOUT ,为了兼容某些 APP 把它们一起返回。

APP 调用poll后,很有可能会休眠。在按键驱动的中断服务程序中,也要有唤醒操作,也是通过内核提供的wake_up_interruptible函数从等待队列中唤醒。

所以中断服务函数不用做任何改动,主要是要实现drv_poll驱动函数:

tu
如上图所示代码,在file_operations结构体中增加poll成员,并用gpio_key_drv_poll初始化,在该函数中完成那两件事情:

  • 将当前线程放入等待队列gpio_key_wait,和前面休眠唤醒的是一个队列,但是不进行休眠。
  • 返回按键数据状态,根据环形缓冲区是否为空,决定返回0,还是(POLLOUT | POLLWRNORM)

可以看到,poll机制的驱动程序就是这么简单,此时就实现了,比较复杂一点的是应用层的程序。

🏸应用编程

应用层poll系统调用的用法,自行查看man手册,或者移步本喵的文章poll,本喵这里就直接用了。

图
如上图所示代码,由于我们只检测一个按键,所以struct pollfd fds[1]只有一个元素即可,设置超时时间为5s。

将驱动设备使用open打开后,将得到的fd作为监测目标填充到struct pollfd结构体中,包括检测的事件是POLLIN,和驱动程序中的drv_poll相对应。

全部配置好后,使用poll获取按键数据的状态:

  • 没有按键数据,程序则阻塞在poll处休眠一会儿,等待被唤醒。
  • 被唤醒后:
    • 如果是因为超时唤醒,则poll得到的数据状态就是0,则打印超时。
    • 如果是因为数据到来唤醒,则poll得到是数据状态就是(POLLOUT | POLLWRNORM),使用read读取按键数据,此时read必然不会阻塞。

演示:

最后将驱动程序和应用程序上传到服务器进行编译,并且将生成的gpio_key_drv.kobutton_test拷贝到网络文件系统中。

图
如上图所示,将驱动程序安装好以后,在开发板运行测试程序后:

  • drv_poll驱动程序先运行一次查看按键数据状态,发现没有数据以后休眠一会儿。
  • 迟迟没有按键按下,被超时唤醒,打印timeout
  • 当按键数据按下后,被数据唤醒后,打印按键数据。

🏓异步通知

如果APP在读取按键数据的过程中不想休眠怎么办?使用异步通知,也就是当有了按键数据以后,驱动程序主动通知APP有数据了,此时APP去读取数据即可,平时APP可以干其他事情。

图
如上图所示使用异步通知的流程图。

  1. 使用open系统调用打开驱动,得到驱动的文件描述符fd
  2. 使用signal系统调用为SIGIO信号注册信号处理函数func
    • 按键驱动程序发出的信号是SIGIO信号,表示有数据输入。
    • APP收到SIGIO信号后,处理函数func就会被自动调用。
  3. 使用fcntl将当前进程的PID设置到内核文件系统中的struct file结构体中,方便后面驱动程序找到进程。
  4. 读取驱动程序文件的Flag
  5. 设置Flag里面的FASYNC位为 1:
    • Flag也是记录到内核文件系统的struct file结构体中,驱动程序通过struct file* filp指针可以获取该标志。
    • FASYNC位发生变化时,内核文件系统就会调用驱动程序的drv_fasync函数。
  6. drv_fasync是否调用是由FASYNC标志位决定的,应用层并没有相应的fasync函数。
  7. drv_fasync函数中,调用内核提供的faync_helper函数,它会根据FAYSNC的值决定是否设置button_async->fa_file=驱动文件filp
    • 驱动文件filp结构体里面含有之前设置的PID。
    • on就代表是着FAYSNC位,它为1则设置button_async,它为0则不设置。
  8. APP可以做其他事情。
  9. 按键按下后,产生按键中断,调用中断服务函数。
  10. 在中断服务函数中调用内核提供的kill_fasync函数,向APP发送信号:
    • 如果button_async->fa_file非空,则从它指向的filp的结构体中取出进程的PID,向该线程发送SIGIO信号。
    • 如果button_async->fa_file为空,则该函数什么都不做,不会发送任何信号。
  11. 在按键中断服务程序中发送SIGIO信号后,信号处理函数func被调用。
  12. func中使用read系统调用读取按键数据。
  13. 最终会调用驱动层中的drv_read读取按键数据,此时一定是有数据的,并不会休眠。

在整个流程中可以看到,FASYNC的变化是为了启动drv_fasync,而真正做事情的是fasync_helper函数。

🏸驱动编程

使用异步通知时,驱动程序的核心有两步:

  • 提供对应的drv_fasync函数。
  • 并在合适的时机发信号,这一点在按键中断服务程序中完成。

图
如上图所示,创建一个struct fasync_struct*类型的全局指针变量button_fasync,该变量专门用来根据进程PID找到对应的进程的。

file_operations结构体中增加成员fasync,并且使用gpio_key_drv_fasync初始化,在该函数中,只需要调用fasync_helper函数即可完成异步通知的记录工作。

图
如上图所示,在按键的中断服务函数中,通过内核提供的kill_fasync函数,向button_fasync中PID代表的线程发送SIGIO信号。

🏸应用编程

异步通知比较复杂的部分是在应用层的测试函数中:

图
如上图所示代码,首先定义一个信号处理函数sig_func函数,当该进程接收到信号后就调用该函数,此时一定是有按键数据的,所以直接调用read读取按键数据即可。

main函数中,使用signal系统调用为SIGIO信号注册处理函数sig_func,当按键按下后,驱动程序就会发出SIGIO信号,处理函数就会被调用。

但是驱动程序怎么知道要将SIGIO信号发给哪个进程呢?此时就需要使用fcntl系统调用,将当前进程的PID和异步通知标志位FASYCN设置到内核文件系统的struct file结构体中。

此时由于FASYCN发发生了变动,触发了驱动层中的drv_fasync被调用,从而导致fasync_helper被调用。

此时异步通知就设置完毕了,APP就可以在while循环中干其他事情了,根本不用关心按键,因为按键会通过信号主动通知APP。

演示:

编译等过程本喵就不啰嗦了,直接演示:

图
如上图所示,运行测试程序后:

  • 没有按键数据,APP在处理其他事情,打印I am A Big Miaomi字符串。
  • 按键按下后,在信号处理函数中读取按键数据并且打印出来。

🏓阻塞与非阻塞

所谓阻塞,就是等待某件事情发生。比如调用read读取按键时,如果没有按键数据则read函数不会返回,它会让线程休眠等待。

此时的休眠是我们在驱动程序中通过内核提供的wait_event_xxx函数实现的。

能不能让read函数既能工作于阻塞方式,也可以工作于非阻塞方式?可以!

  • 在APP中使用open函数时,可以传入O_NONBLOCK,就表示要使用非阻塞方式读取数据。默认情况是阻塞方式读取。
  • 在APP中open一个文件之后,也可以通过fcntl系统调用修改为阻塞或非阻塞读取数据。
  • 对于字符设备文件,O_NONBLOCK起作用的前提是驱动程序针对O_NONBLOCK做了处理。

🏸应用编程

这里本喵先来实现应用层编程:

tu
如上图所示代码,它分为如下几步:

  • 在使用open打开驱动设备时,传入O_NONBLOCK标志,使用非阻塞访问的方式打开。
  • 然后不管有没有按键数据,使用非阻塞方式读取十次按键数据:
    • 有按键数据则返回按键数据,并且打印。
    • 没有按键数据则直接返回,并且打印-1。
  • 使用fcntl将驱动设备设置为阻塞访问方式。
  • while死循环中,使用read阻塞读取按键数据:
    • 有按键数据则返回按键数据,并且打印。
    • 没有按键数据则阻塞不动,不会打印内容。

🏸驱动编程

图
如上图代码所示,需要在驱动层的drv_read中作一些改动:

  • 因为非阻塞方式在应用层设置的,并且O_NONBLOCK标志设置到内核文件系统的struct file结构体中的。
  • 所以在drv_read驱动程序中,如果是非阻塞方式,并且没有按键数据,则不阻塞直接返回-EAGAIN
  • 如果是非阻塞方式,且有按键数据,则并不会将当前线程放入到等待队列中,而是返回按键数据。

此时驱动程序就支持阻塞和非阻塞两种访问方式了。

演示:

图
如上图所示,在开发板上运行起测试程序以后:

  • 先非阻塞读取按键数据十次,由于此时没有按键数据,所以返回的都是-1。
  • 然后开始阻塞读取按键数据,有按键数据时打印,没有按键数据时阻塞不动。
  • 可以看到,我们在驱动程序中,这几种读取按键的方式都提供了。
  • 应用层可以使用任意一种,我们不能指定应用层使用哪种方式。
  • 驱动开发的原则只提供能力,不提供策略

🏓定时器

所谓定时器,就是闹钟,时间到后就要做某些事。有两个要素:时间、做 事,换成程序员的话就是:超时时间、函数。

本喵这里讲解的定时器是软件定时器,它依赖系统的滴答定时器。在内核中使用定时器比较简单,涉及到一些内核提供的函数:

函数说明
setup_timer(timer, fn, data)设置定时器,主要是初始化 timer_list 结构体,设置其中的函数、超时事件等参数。
void add_timer(struct timer_list *timer)向内核添加定时器。
int mod_timer(struct timer_list *timer, unsigned long expires)修改定时器的超时时间
int del_timer(struct timer_list *timer)删除定时器。

当超时时间到达,内核就会调用这个超时函数: timer->function(timer->data)

图

编译内核时,可以在内核源码根目录下用ls -a看到一个隐藏文件,它就 是内核配置文件。打开后可以看到CONFIG_HZ=100,它表示内核每秒中会发生 100 次系统滴答中断(tick)。

每发生一次 tick 中断,全局变量jiffies就会累加1,定时器的时间就是基于 jiffies的,我们修改超时时间时,一般使用这2种方法:

  • add_timer之前,直接修改:
    • timer.expires = jiffies + xxx; // xxx 表示多少个滴答后超时,也就是 xxx*10ms
    • timer.expires = jiffies + 2*HZ; // HZ 等于 CONFIG_HZ,2*HZ 就相当于 2 秒
  • add_timer之后,使用mod_timer修改:
    • mod_timer(&timer, jiffies + xxx); // xxx 表示多少个滴答后超时,也就是 xxx*10ms
    • mod_timer(&timer, jiffies + 2*HZ); // HZ 等于 CONFIG_HZ,2*HZ 就相当于 2 秒

🏸定时器消抖

具体的原理本喵就不再讲解了,主要就是按下一次按键后会多次触发按键中断,由于本喵设置的是双边沿触发,所以触发的会更多,通过定时器来消除这个按键抖动。

图
如上图所示,在描述按键中断的结构体中,再增加一个struct timer_list类型的成员key_timer,因为一个每一个按键都需要有一个定时器来消抖。

图
如上图所示,在probe函数中,对每个按键中断初始化后再初始化其定时器:

  • 使用setup_timer初始化定时器,其中key_timer_expire是超时函数,最后一个参数是超时函数的参数,把按键传进去,方便使用。
  • 将超时时间设置为最大,如果是0的话,定时器一被添加到内核中就会执行超时函数。
  • 使用add_timer将定时器添加到内核中。

图
如上图所示,在卸载驱动程序的时候,使用del_timer将按键的定时器删除掉。

图

如上图所示按键中断服务函数,此时读取按键数据就不在这里进行了,这里仅修改超时时间:

  • 使用mod_timer修改超时时间,每发生一次抖动定时器超时向后推迟200ms。

图
如上图所示,在定时器的超时函数key_timer_expire中做了原本在按键中断服务函数中的事情:

  • 构建按键数据,将其放入到环形缓冲区中。
  • 从等待队列中唤醒休眠的线程。
  • 向进程发送SIGIO信号。

还增加了一些打印语句,用于查看定时器超时函数和按键中断服务函数的执行次数。

演示:

只需要将驱动程序编译后重新安装到开发板上,运行之前的测试程序即可:

图
如上图所示,此时就达到了消抖的功能:

  • 按键按下后,按键中断服务函数执行了两次,定时器两次推迟超时200ms。
  • 按键稳定后,定时器超时函数执行一次,打印按键数据。
  • 应用层也只读取了一次按键数据。

🏓总结

休眠唤醒,POLL机制,异步通知,阻塞与非阻塞都属于驱动程序的基石,非常的重要,后面的复杂的驱动程序也是依赖于这几个机制实现的。

不用应用层是否使用这几个机制,我们在驱动程序中需要全部提供,要遵守提供能力,不提供策略的原则。

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

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

相关文章

VC++中使用OpenCV进行形状和轮廓检测

VC中使用OpenCV进行形状和轮廓检测 在VC中使用OpenCV进行形状和轮廓检测,轮廓是形状分析以及物体检测和识别的有用工具。如下面的图像中Shapes.png中有三角形、矩形、正方形、圆形等,我们如何去区分不同的形状,并且根据轮廓进行检测呢&#…

re:从0开始的HTML学习之路 2. HTML的标准结构说明

1. <DOCTYPE html> 文档声明&#xff0c;用于告诉浏览器&#xff0c;当前HTML文档采用的是什么版本。 必须写在当前HTML文档的首行&#xff08;可执行代码的首行&#xff09; HTML4的此标签与HTML5不同。 2. <html lang“en”> 根标签&#xff0c;整个HTML文档中…

基于SpringBoot的SSM整合案例

项目目录: 数据库表以及表结构 user表结构 user_info表结构 引入依赖 父模块依赖: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.12.RELEASE</version>…

LINUX文件fd(file descriptor)文件描述符

目录 1.文件接口 1.1open 1.2C语言为什么要对open进行封装 2.fd demo代码 第一个问题 第二个问题 打开文件流程 引言&#xff1a;在学习C语言的时候&#xff0c;我们见过很多的文件的接口&#xff0c;例如fopen&#xff0c;fwrite&#xff0c;fclose等等&#xff0c;但…

Mac NTFS 磁盘读写工具选哪个好?Tuxera 还是 Paragon?

在使用 Mac 电脑时&#xff0c;我们经常需要读写 NTFS 格式的硬盘或 U 盘。然而&#xff0c;由于 Mac 系统不支持 NTFS 格式的读写&#xff0c;因此我们需要借助第三方工具来实现这个功能。而在市场上&#xff0c;Tuxera 和 Paragon 是两款备受推崇的 Mac NTFS 磁盘读写工具。那…

CPMS靶场练习

关键&#xff1a;找到文件上传点&#xff0c;分析对方验证的手段 首先查看前端发现没有任何上传的位置&#xff0c;找到网站的后台&#xff0c;通过弱口令admin 123456可以进入 通过查看网站内容发现只有文章列表可以进行文件上传&#xff1b;有两个图片上传点 图片验证很严格…

HCIP-BGP选路实验

一.实验拓扑图 二.详细配置 R1 interface GigabitEthernet0/0/0 ip address 12.1.1.1 255.255.255.0interface LoopBack0 ip address 1.1.1.1 255.255.255.0interface LoopBack1 ip address 10.1.1.1 255.255.255.0bgp 1 router-id 1.1.1.1 peer 12.1.1.2 as-number 2ipv4-fa…

websocket实现聊天室(vue2 + node)

通过websocket实现简单的聊天室功能 需求分析如图&#xff1a; 搭建的项目结构如图&#xff1a; 前端步骤&#xff1a; vue create socket_demo (创建项目)views下面建立Home , Login组件路由里面配置路径Home组件内部开启websocket连接 前端相关组件代码&#xff1a; Login…

CVE重要通用漏洞复现java phpCVE-2021-44228

在进行漏洞复现之前我们需要在linux虚拟机上进行docker的安装 我不喜欢win上安因为不知道为什么总是和我的vmware冲突 然后我的kali内核版本太低 我需要重新安装一个新的linux 并且配置网络 我相信这会话费我不少时间 查看版本 uname -a 需要5.5或以上的版本 看错了浪…

微信小程序-03

小程序官方把 API 分为了如下 3 大类&#xff1a; 事件监听 API 特点&#xff1a;以 on 开头&#xff0c;用来监听某些事件的触发 举例&#xff1a;wx.onWindowResize(function callback) 监听窗口尺寸变化的事件 同步 API 特点1&#xff1a;以 Sync 结尾的 API 都是同步 API 特…

使用多进程库计算科学数据时出现内存错误

问题背景 我经常使用爬虫来做数据抓取&#xff0c;多线程爬虫方案是必不可少的&#xff0c;正如我在使用 Python 进行科学计算时&#xff0c;需要处理大量存储在 CSV 文件中的数据。由于每个处理过程需要很长时间才能完成&#xff0c;而您拥有多核处理器&#xff0c;所以您尝试…

SpringBoot教务管理源码

技术框架&#xff1a; springboot mybatis layui shiro jquery react 运行环境&#xff1a; jdk8 mysql5.7 IntelliJ IDEA maven nginx 系统介绍&#xff1a; 教务管理系统是一个基于网络的在线管理平台 , 帮助学校管理教务系统&#xff0c; 用一个账号解决学校教…

聪明的小羊肖恩(双指针)

题目 import java.util.Arrays; import java.util.Scanner;public class Main {public static long calc(long[] num,int max) { int l 0;int r num.length-1;long res 0;while(l<r) {while(l<r && num[l]num[r]>max) {r--;}res(r-l);l;}return res;}pub…

LeetCode---380周赛

题目列表 3005. 最大频率元素计数 3006. 找出数组中的美丽下标 I 3007. 价值和小于等于 K 的最大数字 3008. 找出数组中的美丽下标 II 一、最大频率元素计数 这题就是个简单的计数题&#xff0c;正常遍历统计数据即可&#xff0c;关键是你要会写代码逻辑。 代码如下&…

CocoaPods的安装和使用

前言 本篇文章讲述CocoaPods的安装和使用 安装cocoaPods 如果电脑没有安装过cocoaPods&#xff0c;需要先安装&#xff0c;使用下面的命令&#xff1a; sudo gem install cocoapods输入密码后开始安装&#xff0c;需要等待。。。但是我这里报错了。 The last version of d…

本地读取Excel文件并进行数据压缩传递到服务器

在项目开发过程中&#xff0c;读取excel文件&#xff0c;可能存在几百或几百万条数据内容&#xff0c;那么对于大型文件来说&#xff0c;我们应该如何思考对于大型文件的读取操作以及性能的注意事项。 类库&#xff1a;Papa Parse - Powerful CSV Parser for JavaScript 第一步…

被施工现场折磨哭的我,真后悔没早点用这个方法!

在当今数字化时代&#xff0c;智慧工地的概念越来越受到重视。通过整合先进的技术和创新的解决方案&#xff0c;智慧工地不仅提高了工程施工效率&#xff0c;还加强了安全管理和资源利用。 客户案例一 广州某建筑公司在项目中部署了泛地缘科技推出的智慧工地大数据平台&#x…

ORB-SLAM 论文阅读

论文链接 ORB-SLAM 0. Abstract 本文提出了 ORB-SLAM&#xff0c;一种基于特征的单目同步定位和建图 (SLAM) 系统该系统对严重的运动杂波具有鲁棒性&#xff0c;允许宽基线环路闭合和重新定位&#xff0c;并包括全自动初始化选择重建的点和关键帧的适者生存策略具有出色的鲁棒…

浅谈大数据智能化技术在多个领域的应用实践

摘要 大数据智能化技术在当今信息社会中得到了广泛的应用。从金融、互联网电商、视频行业到垂直短视频领域&#xff0c;从工业互联网到云计算、边缘计算等领域&#xff0c;大数据智能化技术已经成为了企业竞争力的重要组成部分。技术实践、架构设计、指标体系、数据质量、数据分…

系统架构设计师教程(十二)信息系统架构设计理论与实践

信息系统架构设计理论与实践 12.1 信息系统架构基本概念及发展12.1.1 信息系统架构的概述12.1.2 信息系统架构的发展12.1.3 信息系统架构的定义 12.2 信息系统架构12.2.1 架构风格12.2.2 信息系统架构分类12.2.3 信息系统架构的一般原理12.2.4 信息系统常用4种架构模型12.2.5 企…