我在ARM板上写的第一个驱动程序

有时大家喜欢拿电灯来当作笑谈,实际上点灯包含多内容很多,如下这篇文章就是关于嵌入式Linux点灯多技术栈,推荐给大家。


摘要:搞嵌入式有两个方向,一个是嵌入式软件开发(MCU方向),另一个是嵌入式软件开发(Linux方向)。其中MCU方向基本是裸机开发和RTOS开发。而Linux开发方向又分为驱动开发和应用开发。其中应用开发相比于驱动开发来说简单一些,因为搞驱动你要和Linux内核打交道。而我们普通的单片机开发就是应用开发,和Linux开发没多大区别,单片机你去调别人写好的库,Linux应用你也是调别人的驱动程序。

很多人学习的路线是:单片机到RTOS,再到Linux,这个路线其实是非常好,循序渐进。因为你学了单片机,所以你对RTOS的学习会很容易理解,单片机+RTOS在市面上也可以找到一个很好的工作。因为你学了RTOS,你会发现Linux驱动开发其实和RT-Thread的驱动程序非常像,其实RT-Thread驱动大概率可能是仿Linux驱动而写的。所以如果你现在在学RT-Thread,那么你后面去搞Linux驱动也是非常容易上手。

当然做驱动去之前你还是要学习一下ubuntu操作系统、ARM裸机和linux系统移植,其目的就是为学习嵌入式linux驱动开发做准备。

话不多说先来一个hello驱动程序。

在Linux中,驱动分为三大类:

  • 字符设备驱动

    • 字符设备驱动是占用篇幅最大的一类驱动,因为字符设备最多,从最简单的点灯到 I2C、SPI、音频等都属于字符设备驱动的类型。

  • 块设备驱动

    • 块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都给我们编写好了,大多数情况下都是直接可以使用的。

    • 所谓的块设备驱动就是存储器设备的驱动,比如 EMMC、NAND、SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备。

  • 网络设备驱动

    • 网络设备驱动很好理解,不管是有线的还是无线的,都属于网络设备驱动的范畴。一个设备可以属于多种设备驱动类型,比如 USB WIFI,其使用 USB 接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。

我使用的Linux内核版本为 4.1.15,其支持设备树Device tree。开发板是正点原子送的Linux-MINI板,你用其他家的板子也是一样的,没有任何影响。

bbca64b771c98eb30a512eb7d25e7ba6.png

一、字符设备驱动简介

字符设备是Linux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

那么在Linux下的应用程序是如何调用驱动程序的呢?Linux 应用程序对驱动程序的调用如图所示:

e8f29ab4e065675cd166af2be8278a3b.png
Linux应用程序对驱动程序的调用流程

在Linux 中一切皆为文件,驱动加载成功以后会在/dev目录下生成一个相应的文件,应用程序通过对这个名为/dev/xxx(xxx是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

写驱动的人必须要懂linux内核,因为驱动程序就是根据内核的函数去写的,写应用的人不需要懂linux内核,只需要熟悉驱动函数就可以了。

比如现在有个叫做/dev/led的驱动文件,是led灯的驱动文件。应用程序使用open函数来打开文件/dev/led,使用完成以后使用close函数关闭/dev/led 这个文件。open和 close 就是打开和关闭led驱动的函数,如果要点亮或关闭led,那么就使用write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开led的控制参数。如果要获取led 灯的状态,就用 read 函数从驱动中读取相应的状态。

应用程序运行在用户空间,而Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用open函数打开/dev/led这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作

open、close、write 和read等这些函数是由C库提供的,在Linux系统中,系统调用作为C库的一部分。当我们调用 open 函数的时候流程如图所示:

f4d62ae2f03d72a3257d268fcfb0a89a.png
open函数调用流程

其中关于C库以及如何通过系统调用“陷入”到内核空间这个我们不用去管,我们关注的是应用程序和具体的驱动,应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了open这个函数,那么在驱动程序中也得有一个名为open的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数。

在Linux内核文件include/linux/fs.h中有个叫做file_operations的结构体,此结构体就是Linux内核驱动操作函数集合,我们可以将linux内核文件下载下来,然后用source insight打开看看。内容如下所示:

点击此处下载linux内核源码

5441f5d5fbf6bf0ee4f5c4d2f28066fd.png38897ae611a9e682f96b5f2a9dc5977e.pngcee37b173a7cefa35f053c9294579f00.png
Linux内核
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*mremap)(struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endif
};
39e116f2e4a7a3301285efe3f0c7177a.png
  • 第 1589 行,owner 拥有该结构体的模块的指针,一般设置为THIS_MODULE

  • 第 1590 行,llseek函数用于修改文件当前的读写位置。

  • 第 1591 行,read函数用于读取设备文件

  • 第 1592 行,write函数用于向设备文件写入(发送)数据

  • 第 1596 行,poll是个轮询函数,用于查询设备是否可以进行非阻塞的读写。

  • 第 1597 行,unlocked_ioctl函数提供对于设备的控制功能,与应用程序中的ioctl函数对应。

  • 第 1598 行,compat_ioctl函数与unlocked_ioctl函数功能一样,区别在于在64位系统上,32位的应用程序调用将会使用此函数。在32位的系统上运行32位的应用程序调用的是unlocked_ioctl。

  • 第 1599 行,mmap函数用于将将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如LCD驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。

  • 第 1601 行,open 函数用于打开设备文件。

  • 第 1603 行,release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。

  • 第 1604 行,fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。

  • 第 1605 行,aio_fsync函数与 fasync 函数的功能类似,只是aio_fsync是异步刷新待处理的数据。

二、字符设备驱动开发

学习裸机或者STM32的时候关于驱动的开发就是初始化相应的外设寄存器,在Linux驱动开发中肯定也是要初始化相应的外设寄存器,这个是毫无疑问的。只是在Linux驱动开发中我们需要按照其规定的框架来编写驱动,所以说学Linux驱动开发重点是学习其驱动框架

2.1 APP打开的文件在内核中如何表示

APP使用open函数打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于APP的每一个文件句柄,在内核里面都有一个struct file与之对应。

4bf561cf9516673328bfe7097c4c65c5.png
struct file

我们使用open打开文件时,传入的 flags、mode等参数会被记录在内核中对应的struct file结构体里(f_flags、f_mode):

int open(const char *pathname, int flags, mode_t mode);

去读写文件时,文件的当前偏移地址也会保存在struct file结构体的f_pos成员里。

435a71e74603e51685127568571de3f5.png
open->struct file

打开字符设备节点时,内核中也有对应的struct file注意这个结构体中的结构体:struct file_operations *f_op,这是由驱动程序提供的。

d31e5372a850ad1c922adc9d7199d07c.png
驱动程序的 struct file
c7312bc909c96ae6f811a1faa8e7d016.png
驱动程序的 open/read/write

结构体struct file_operations的定义如下,上面也讲过了。

6c3e0f047a6ee9b76cb36d6b725e54b3.png

2.2 编写驱动程序的步骤

  • 1、确定主设备号,也可以让内核分配。

  • 2、定义自己的file_operations结构体。

  • 3、实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体。

  • 4、把file_operations结构体告诉内核:register_chrdev

  • 5、谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数。

  • 6、有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev。

  • 7、其他完善:提供设备信息,自动创建设备节点:class_create,device_create。

2.3 试验程序编写

应用程序调用open函数打开hello_drv这个设备,打开以后可以使用write 函数向hello_drv的写缓冲区writebuf中写入数据(不超过 100 个字节),也可以使用read函数读取读缓冲区readbuf中的数据操作,操作完成以后应用程序使用close函数关闭chrdevbase设备。

hello_drv.c

#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>/* 1. 确定主设备号*/
static int major = 200;
static char kernel_buf[1024];
static struct class *hello_class;#define MIN(a, b) (a < b ? a : b)/* 3. 实现对应的open/read/write等函数,填入file_operations结构体  */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_to_user(buf, kernel_buf, MIN(1024, size));return MIN(1024, size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(kernel_buf, buf, MIN(1024, size));return MIN(1024, size);
}static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static int hello_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的file_operations结构体*/
static struct file_operations hello_drv = {.owner  = THIS_MODULE,.open    = hello_drv_open,.read    = hello_drv_read,.write   = hello_drv_write,.release = hello_drv_close,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{int retvalue;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);retvalue = register_chrdev(major, "hello_drv", &hello_drv);  /* /dev/hello */if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void __exit hello_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello_drv");
}/* 7. 其他完善:提供设备信息,自动创建设备节点   */
module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhiguoxin");

2.4 测试程序编写

驱动编写好以后是需要测试的,一般编写一个简单的测试APP,测试APP运行在用户空间。测试APP很简单通过输入相应的指令来对hello_drv设备执行读或者写操作。

hello_drv_test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/*
app测试
./hello_drv_test -w www.zhiguoxin.cn
./hello_drv_test -r
*/
int main(int argc, char **argv)
{int fd;char buf[1024];int len;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf("       %s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/hello", O_RDWR);if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 写文件或读文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024);  buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}

这里的代码很简单就不用再说了,这是linux应用开发的知识。

2.5 编写Makefile

KERNELDIR := /home/zhiguoxin/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := hello_drv.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules$(CROSS_COMPILE)arm-linux-gnueabihf-gcc -o hello_drv_test hello_drv_test.c 
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
  • 第1行,KERNELDIR表示开发板所使用的Linux内核源码目录,使用绝对路径,大家根据自己的实际情况填写。

  • 第2行,CURRENT_PATH表示当前路径,直接通过运行pwd命令来获取当前所处路径。

  • 第3行,obj-m表示将hello_drv.c这个文件编译为hello_drv.ko模块。

  • 第8行,具体的编译命令,后面的modules表示编译模块,-C表示将当前的工作目录切换到指定目录中,也就是KERNERLDIR目录。M表示模块源码目录,make modules命令中加入M=dir以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。

  • 第9行,使用交叉编译工具链将hello_drv_test.c编译成可以在arm板子上运行的hello_drv_test可执行文件。

Makefile 编写好以后输入make命令编译驱动模块,编译过程如图所示

bf384577d33d73d55f2ad9f233f72728.png

有时候你可能遇到下面的错误

93422e426e530e0e9596016430d8bc64.png

这个错误的原因是ubuntu中的linux源码没有编译导致的,使用下面的命令将源码编译一遍就好了。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

编译成功以后就会生成一个叫做hello_drv.ko的文件,此文件就是hello_drv设备的驱动模块。至此,hello_drv设备的驱动就编译成功。

d94073a0451e04b299fc61d85c4d2c71.png

2.6 运行测试

2.6.1 上传程序到开发板执行

开发板启动后通过NFS挂载Ubuntu目录的方式,将相应的文件拷贝到开发板上。简单来说,就是通过NFS在开发板上通过网络直接访问ubuntu虚拟机上的文件,并且就相当于自己本地的文件一样。

因为我的代码都放在/home/zhiguoxin/myproject/alientek_drv_development_source这个目录下,所以我们将这个目录作为NFS共享文件夹。

f881a337c35dcec4e1b7823c8c7094d4.png10041d4caefe27e139702d7258a844fc.png

Ubuntu IP为192.168.10.100,一般都是挂载在开发板的mnt目录下,这个目录是专门用来给我们作为临时挂载的目录。

2bb167baedf7e44666a72730a5cdcbe3.png
文件系统目录简介

然后使用MobaXterm软件通过SSH访问开发板。

ubuntu ip:192.168.10.100
windows ip:192.168.10.200
开发板ip:192.168.10.50

在开发板上执行以下命令就可以实现挂载了:

mount -t nfs -o nolock,vers=3 192.168.10.100:/home/zhiguoxin/myproject/alientek_drv_development_source /mnt

就将ARM板的mnt目录挂载在ubuntu的/home/zhiguoxin/myproject/alientek_drv_development_source目录下了。这样我们就可以在Ubuntu下修改文件,然后可以直接在开发板上执行可执行文件了。当然我这里的/home/zhiguoxin/myproject/windows之间是一个共享目录,我也可以直接在windows上面修改文件,然后ubuntu和开发板直接进行文件同步了。

0b32d31859d07256af204afced0a114f.png

2.6.2 加载驱动模块

驱动模块hello_drv.kohello_drv_test可执行文件都已经准备好了,接下来就是运行测试。这里我是用挂载的方式将服务端的项目文件夹挂载到arm板的mnt目录,进入到/mnt/01_hello_drv目录输入如下命令加载hello_drv.ko驱动文件:

insmod hello_drv.ko
a3e633b15a9607c0d959e76623bbc06d.png

如果模块加载成功,不会有任何提示,如果失败会有提示,可能会出错的是你的模块版本和你的arm板内版本不一致。

输入lsmod命令即可查看当前系统中存在的模块

lsmod
425e91a7abcaaf04f3a4a79bbb9cbd07.png

当前系统只有hello_drv这一个模块。输入如下命令查看当前系统中有没有hello_drv这个设备:

cat /proc/devices
31fa887de4fd063da230b3d43252b526.png

可以看出,当前系统存在hello_drv这个设备,主设备号为200,跟我们设置的主设备号一致。

2.7 创建设备节点文件

驱动加载成功需要在/dev目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/hello_drv这个设备节点文件:

mknod /dev/hello_drv c 200 0

其中mknod是创建节点命令,/dev/hello_drv 是要创建的节点文件,c表示这是个字符设备,200是设备的主设备号,0是设备的次设备号。创建完成以后就会存在/dev/hello_drv 这个文件,可以使用ls /dev/chrdevbase -l命令查看

ls /dev/hello_drv -l
ba3725dcc67ce0b751a877c9fadf2e5d.png

如果hello_drv_test想要读写hello_drv设备,直接对/dev/hello_drv进行读写操作即可。相当于/dev/hello_drv这个文件是hello_drv设备在用户空间中的实现。Linux下一切皆文件,包括设备也是文件,现在大家应该是有这个概念了吧?

2.8 hello_drv设备操作测试

一切准备就绪。使用hello_drv_test软件操作hello_drv这个设备,看看读写是否正常,首先进行写操作,将字符串输入www.zhiguoxin.cn写入到内核中

./hello_drv_test -w www.zhiguoxin.cn

然后再从内核中将刚写入的字符串读出来

./hello_drv_test -r
3712db9e5a99428fd775977c11c7b1e9.png

可以看到读写正常,说明我们编写的hello_drv驱动是没有问题的。

2.9 卸载驱动模块

如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉 hello_drv这个设备:

rmmod hello_drv.ko

卸载以后使用lsmod命令查看hello_drv这个模块还存不存在:

90da7801a8592a0648a3d98675437dee.png

可以看出,此时系统已经没有任何模块了,hello_drv这个模块也不存在了,说明模块卸载成功。而且系统中也没有了hello_drv这个设备。

至此,hello_drv这个设备的整个驱动就验证完成了,驱动工作正常。以后的字符设备驱动实验基本都可以此为模板进行编写。

总结

上面就是Linux中的字符驱动,可能初学者看起来还有点难,这里我并没有讲解代码,因为没有什么好讲的,就是我前面在单片机开发中的常说的面向对象编程和指针函数的实际运用,所以做嵌入式还是要把C语言的基础打牢,尤其是结构体、指针和链表,如果这三个你能很好的理解那么Linux驱动编程就非常容易,因为驱动开发就=软件架构+硬件操作。而软件架构就需要你要非常熟悉C语言,硬件操作就是你单片机的那几个寄存器操作。当然如果你学了RT-Thread那么学习Linux驱动也是非常容易的,因为RT-Thread是内核+驱动,而FreeRTOS仅仅只是一个内核而已。

end

果果小师弟

关注,回复【更多资料】海量嵌入式资料赠送

精彩文章合集

文章推荐

☞【专辑】电赛电源题系列

☞【专辑】嵌入式开源项目

☞【专辑】STM32系列教程

☞【专辑】QT/上位机教程

☞【专辑】电子电路/美图/奇趣

☞【专辑】程序人生/大佬自述

☞【专辑】个人网站博客搭建

觉得好看,请点这里

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

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

相关文章

string、char *、char []之间的相互转换

最近工作中遇到了string、char *、char []之间的相互转换&#xff0c;今天终于抽出时间将他们之间的转换记录下来&#xff0c;使用的是CodeBlocks软件&#xff0c;编译器为GNU GCC compiler&#xff0c;下面看代码&#xff1a; #include <iostream> #include <stdio.…

C语言中匿名的最高境界

C语言中有没有见过(int [2]){19,20}或者int (*pt2)[4]的使用方法&#xff0c;字面上可能不好理解&#xff0c;这是C99之后才新增的知识点&#xff0c;名为复合型表述Compound Literals&#xff0c;一旦熟悉使用&#xff0c;便会体会到它简洁而强大的表达。什么是”复合型表述“…

codeblocks安装后提示找不到编译器

安装了自带编译器的codeblocks&#xff0c;但是打开后提示没有找到compiler&#xff0c; 经过几分钟的搜索&#xff0c;找了原因&#xff1a; 打开codeblocks&#xff0c;进入settings->compiler&#xff0c;选择如下: 然后&#xff0c;选择Toolchain executables&#xff…

一文读懂|栈溢出攻击

什么是栈简单来说&#xff0c;栈 是一种 LIFO&#xff08;Last In Frist Out&#xff0c;后进先出&#xff09; 形式的数据结构。栈一般是从高地址向低地址增长&#xff0c;并且栈支持 push&#xff08;入栈&#xff09; 和 pop&#xff08;出栈&#xff09; 两个操作。如下图所…

Sqlserver 通用存储过程(二) 联合主键

CREATEPROCP_public_ViewPage /**//**//**//* no_mIss 通用分页存储过程 2007.3.1 QQ:34813284 适用于联合主键/单主键/存在能确定唯一行列/存在能确定唯一行的多列 (用英文,隔开) 调用&#xff1a; 第一页查询时返回总记录和总页数及第一…

理解ALSA

最近处理音频的问题&#xff0c;所以看了一些不错的文章&#xff0c;整理一些有用的资料出来&#xff0c;有需要的可以收藏。ALSA的框架图&#xff1a;这个图可以说是我目前看到最不错的&#xff0c;我发现很多应用开发的&#xff0c;一出现解决不了的问题&#xff0c;或者奇怪…

Xshell 6如何设置多个session显示在同一个窗口

刚才安装了Xshell 6之后&#xff0c;发现在同一个窗口只能显示4个session&#xff0c;网上查找了一些资料但是都不是想要的结果&#xff0c;经过几分钟的查找&#xff0c;终于找到了设置在同一个窗口session的个数&#xff0c;因此记录下来&#xff0c;或者给与他人帮助。以下以…

blockUI应用到Asp.Net页面时服务器控件(Button等)失效的问题

问题&#xff1a;在Asp.Net页面中用blockUI这个控件实现弹出窗口的效果&#xff0c;弹出页面内容为页面中某个Panel中的内容&#xff0c;包含TextBox、Button等服务器控件。使用时就简单的设置message属性。问题出来了&#xff0c;当显示这个弹出页面后&#xff0c;所有Button等…

android DatePicker

为什么80%的码农都做不了架构师&#xff1f;>>> public class DatePicker extends FrameLayout java.lang.Object android.view.View android.view.ViewGroup android.widget.FrameLayout android.widget.DatePicker DatePicker 一个选择年月日的日历布局视图 公…

一次限制进程的 CPU 用量的实操过程

大家好&#xff0c;我是飞哥&#xff01;给大家分享一个事情。背景是这样的&#xff0c;我们要测试某个第三方 SDK 运行性能&#xff0c;这是个 CPU 密集型的服务。我想评估一下它运行一遍到底有多吃 CPU&#xff0c;以便评估上线后我们需要部署多少台服务器。我们是在一台 16 …

map与unordered_map的区别

set/map底层实现的机制是红黑树。红黑树是一种近似于平衡的二叉查找树&#xff0c;默认是按升序排序的。在红黑树上做查找、插入、删除操作的时间复杂度为O(logN)。 红黑树的缺点&#xff1a;空间占用率高&#xff0c;每一个节点都需要额外保存父节点、孩子节点和红/黑性质&am…

navicat不同数据库数据传输

复制fo的t_fo_account表结构和数据到base库 结果 转载于:https://www.cnblogs.com/feifeicui/p/10307646.html

收藏了两年的嵌入式AI资源学习笔记,今天全分享给大家(附代码/资料/视频/学习规划)...

当前乃至未来5-10年&#xff0c;嵌入式开发者还有哪些风口&#xff1f;”画外音&#xff1a;风口的本质&#xff0c;其实就是一段时间的人才供需不平衡。说白了就是由于行业突变&#xff0c;敏锐的资本快速进入&#xff0c;导致短时间内行业大量扩张&#xff0c;需要大量开发者…

Vmware由于centos升级内核不可运行(C header files matching your running kernel were not found)的解决方案...

C header files matching your running kernel were not found. Refer to your distributions documentation for installation instructions - NoH4cker - 博客园 http://www.cnblogs.com/NoH4cker/p/4840571.html centos6 安装wmwaretools找不到kernel header - jiejnan - 博…

分享一个消息组件

前段时间在收集项目素材时发现一个很好用的消息组件ymPrompt,顺便收集了圈子里关于这个组件的文章&#xff0c;感觉介绍不是很完善。 废话少说先看一下演示效果: 演示Demo: http://www.ajaxbbs.net/test/ymPrompt4.0/demo.html 截取的图片: Vista样式 简短的实现脚本: Code--导…

用C语言搞机器学习,来个最基础的Knn入门

本来是准备周末加班两天的&#xff0c;然后&#xff0c;临时突然其他事情又取消了。顺便看了下csdn&#xff0c;看到一篇介绍KNN的&#xff0c;因为我现在做的也是属于机器学习方向&#xff0c;那自然也要了解一些这部分。KNN是什么&#xff1f;KNN可以说是最简单的分类算法之一…

最简单的断线断点检测器电路

要在长长的电线中找到究竟是哪里断开了&#xff0c;可以做一个断线断点检测器。而且几个元器件就可以实现&#xff0c;非常简单。这个断线断点检测器不仅可以识别火线、零线&#xff0c;还可以检测电线是哪里断开了。实际是检测哪里的磁场强&#xff0c;哪里的电磁辐射大。来看…

声学发展史之——人工智能(AI)声学

引言最近接手了一个EOL (End of Line)的项目&#xff0c;用高斯混合模型GMM (Gaussian Mixture Model)作生产线上产品的质量检测。虽然提取特征的过程很痛苦&#xff0c;不过还是很有意思。也是因为兴趣&#xff0c;去年在Coursera上了吴恩达的Machine Learning&#xff0c;算是…

一个application多个 URL

需求&#xff1a; 希望一个sharepoint网站&#xff0c;有多个网址去访问。例如&#xff1a;http://moss:8080/的网站&#xff0c;http://aphla.prismshareusa.int/ 和 http://aphla.carat.int/ 两个网站同时访问。 解决方法如下&#xff1a; 1&#xff09;创建DNS…

写的重采样文章被大佬看到了

他让我看重采样昨天写的重采样文章被一个大佬看到了&#xff0c;给我发了消息如下大佬是个技术原厂Linux方向的负责人&#xff0c;我在工作上遇到的好几次疑难杂症都是在他的指点下得到解决&#xff0c;而且平时讨论技术的时候&#xff0c;能感觉到他对技术问题理解很深。从他的…