Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以本章的 LED 灯驱动最终也是对 I.MX6ULLIO 口进行配置,与裸机实验不同的是,在 Linux 下编写驱动要符合 Linux 的驱动框架。

一、地址映射

Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。

在编写驱动之前,我们需要先简单了解一下 MMU 这个神器,MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在 Linux 内核已经支持无MMU的处理器了。MMU主要完成的功能如下:

1、完成虚拟空间到物理空间的映射。

2、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

物理内存和虚拟内存之间的转换,需要用到两个函数:ioremapiounmap

1、ioremap

ioremap 函 数用于获取指定物理地址空间对应的虚拟地 址空 间,定 义在 arch/arm/include/asm/io.h 文件中,定义如下:

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
{return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));
}

2、iounmap

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原型如下:

void iounmap (volatile void __iomem *addr)

二、I/O内存访问函数

这里说的 I/O输入/输出的意思。这里涉及到两个概念I/O 端口I/O 内存

外部寄存器内存映射到 IO 空间时,称为 I/O 端口

外部寄存器内存映射到内存空间时,称为 I/O 内存

但是对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。使用 ioremap 函数将寄存器的物 理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

1、读操作函数

u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)

readbreadwreadl 这三个函数分别对应 8bit16bit32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。

2、写操作函数

void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

writebwritewwritel 这三个函数分别对应 8bit16bit32bit 写操作,参数 value 是要写入的数值,addr 是要写入的地址。

三、硬件原理图分析

https://blog.csdn.net/OnlyLove_/article/details/121757151

四、实验程序编写

1、工程配置

创建工程文件 c_cpp_properties.json,并添加头文件路径。

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include","/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include","/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "gnu17","cppStandard": "gnu++14","intelliSenseMode": "linux-gcc-x64"}],"version": 4
}

2、搭建驱动框架

1、led.c

#include "linux/init.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/types.h"// struct inode 声明在 linux/fs.h 中
// struct file 声明在 linux/fs.h 中
int led_open (struct inode *i, struct file *f)
{// printk 声明在 linux/printk.h 中printk("led open!\r\n");return 0;
}int led_release (struct inode *i, struct file *f)
{printk("led release!\r\n");return 0;
}// ssize_t 定义在 linux/types.h 中
// __user 定义在 linux/compiler.h 中
// size_t 定义在 linux/types.h 中
// loff_t 定义在 linux/types.h 中
ssize_t led_read (struct file *f, char __user *b, size_t c, loff_t * l)
{printk("led read!\r\n");return 0;
}ssize_t led_write (struct file *f, const char __user *b, size_t c, loff_t *l)
{printk("led write!\r\n");return 0;
}// 声明在 linux/fs.h 头文件中
static struct file_operations fops = {.open = led_open,.release = led_release,.read = led_read,.write = led_write,
};/* 驱动入口函数 */
static int __init led_init(void)
{/* 入口函数具体内容 */int retvalue = 0;// 声明在 linux/fs.h 头文件中retvalue = register_chrdev(200,"chrdev",&fops);if(retvalue < 0){/* 字符设备注册失败 */}return 0;
}/* 驱动出口函数 */
static void __exit led_exit(void)
{/* 出口函数具体内容 */// 声明在 linux/fs.h 头文件中unregister_chrdev(200,"chrdev");
}// 声明在 linux/init.h 头文件中
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(led_init);
module_exit(led_exit);// 声明在 linux/module.h 头文件中
MODULE_LICENSE("GPL");

2、makefile

KERNELDIR := /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := led.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

3、LED 初始化

1、映射相关寄存器地址

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE              (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE      (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE      (0X020E02F4)
#define GPIO1_DR_BASE               (0X0209C000)
#define GPIO1_GDIR_BASE             (0X0209C004)/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

以上通过 define 定义物理地址,通过变量保存虚拟地址。物理地址和虚拟地址映射通过 open 函数完成,代码如下:

int led_open (struct inode *i, struct file *f)
{// printk 声明在 linux/printk.h 中printk("led open!\r\n");/* 寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);return 0;
}

取消地址映射通过 close 函数完成,代码如下:

int led_release (struct inode *i, struct file *f)
{printk("led release!\r\n");/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);return 0;
}

2、初始化

初始化包括配置时钟、引脚复用、引脚电平。

初始化代码在 open 函数中完成,添加初始化程序后代码如下:

int led_open (struct inode *i, struct file *f)
{u32 val = 0;// printk 声明在 linux/printk.h 中printk("led open!\r\n");/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); /* 清楚以前的设置 */val |= (3 << 26);   /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能,将其复用为*    GPIO1_IO03,最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);    /* 清除以前的设置 */val |= (1 << 3);    /* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val = readl(GPIO1_DR);val |= (1 << 3);    writel(val, GPIO1_DR);return 0;
}

3、控制 LED 灭亮

控制灯的灭亮,由 write 函数完成。

1、从用户空间拷贝数据

extern inline long
copy_from_user(void *to, const void __user *from, long n)
{return __copy_tofrom_user(to, (__force void *)from, n, from);
}/** to:内核空间数据存储缓存* from:指向用户空间数据缓存区* n:拷贝数据长度*/

2、LED 亮灭控制

#define LEDOFF   0               /* 关灯 */
#define LEDON   1               /* 开灯 *//** @description       : LED打开/关闭* @param - sta   : LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return           : 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);   writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3);  writel(val, GPIO1_DR);}
}

3、write 代码

/** @description    : 向设备写数据 * @param - f  : 设备文件,表示打开的文件描述符* @param - b   : 要写给设备写入的数据* @param - c   : 要写入的数据长度* @param - l     : 相对于文件首地址的偏移* @return         : 写入的字节数,如果为负值,表示写入失败*/
ssize_t led_write (struct file *f, const char __user *b, size_t c, loff_t *l)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;printk("led write!\r\n");retvalue = copy_from_user(databuf, b, c);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];      /* 获取状态值 */if(ledstat == LEDON) { led_switch(LEDON);      /* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF); /* 关闭LED灯 */}return 0;
}

4、驱动代码

#include "linux/init.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/types.h"
#include "asm/io.h"
#include "asm/uaccess.h"#define LED_MAJOR     200     /* 主设备号 */
#define LED_NAME        "led"     /* 设备名字 */#define LEDOFF    0               /* 关灯 */
#define LEDON   1               /* 开灯 *//* 寄存器物理地址 */
#define CCM_CCGR1_BASE              (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE      (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE      (0X020E02F4)
#define GPIO1_DR_BASE               (0X0209C000)
#define GPIO1_GDIR_BASE             (0X0209C004)/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;/** @description       : LED打开/关闭* @param - sta   : LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return           : 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);   writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3);  writel(val, GPIO1_DR);}
}// struct inode 声明在 linux/fs.h 中
// struct file 声明在 linux/fs.h 中
int led_open (struct inode *i, struct file *f)
{u32 val = 0;// printk 声明在 linux/printk.h 中printk("led open!\r\n");/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); /* 清楚以前的设置 */val |= (3 << 26);   /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能,将其复用为*    GPIO1_IO03,最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);    /* 清除以前的设置 */val |= (1 << 3);    /* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val = readl(GPIO1_DR);val |= (1 << 3);    writel(val, GPIO1_DR);return 0;
}int led_release (struct inode *i, struct file *f)
{printk("led release!\r\n");/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);return 0;
}// ssize_t 定义在 linux/types.h 中
// __user 定义在 linux/compiler.h 中
// size_t 定义在 linux/types.h 中
// loff_t 定义在 linux/types.h 中
ssize_t led_read (struct file *f, char __user *b, size_t c, loff_t * l)
{printk("led read!\r\n");return 0;
}/** @description  : 向设备写数据 * @param - f  : 设备文件,表示打开的文件描述符* @param - b   : 要写给设备写入的数据* @param - c   : 要写入的数据长度* @param - l     : 相对于文件首地址的偏移* @return         : 写入的字节数,如果为负值,表示写入失败*/
ssize_t led_write (struct file *f, const char __user *b, size_t c, loff_t *l)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;printk("led write!\r\n");retvalue = copy_from_user(databuf, b, c);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];      /* 获取状态值 */if(ledstat == LEDON) { led_switch(LEDON);      /* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF); /* 关闭LED灯 */}return 0;
}// 声明在 linux/fs.h 头文件中
static struct file_operations fops = {.open = led_open,.release = led_release,.read = led_read,.write = led_write,
};/* 驱动入口函数 */
static int __init led_init(void)
{/* 入口函数具体内容 */int retvalue = 0;// 声明在 linux/fs.h 头文件中retvalue = register_chrdev(LED_MAJOR,LED_NAME,&fops);if(retvalue < 0){/* 字符设备注册失败 */}return 0;
}/* 驱动出口函数 */
static void __exit led_exit(void)
{/* 出口函数具体内容 */// 声明在 linux/fs.h 头文件中unregister_chrdev(LED_MAJOR,LED_NAME);
}// 声明在 linux/init.h 头文件中
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(led_init);
module_exit(led_exit);// 声明在 linux/module.h 头文件中
MODULE_LICENSE("GPL");

5、应用程序代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"int main(int argc, char *argv[])
{int fd = 0, retvalue = 0;char writebuf[1] = "";fd = open(argv[1],O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", argv[1]);return -1;}writebuf[0] = atoi(argv[2]);// 打开 ledwrite(fd, writebuf, 1);retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", argv[1]);return -1;}return 0;
}

编译应用程序指令如下:

arm-linux-gnueabihf-gcc led_app.c -o led_app

6、验证

1、加载驱动

加载驱动

/ # ls
bin      etc      led_app  linuxrc  proc     sbin     tmp
dev      led.ko   lib      mnt      root     sys      usr
/ # insmod led.ko
/ #
/ # lsmod
Module                  Size  Used by    Tainted: G
led                     2274  0

加载驱动也可以使用 modprobe 命令。

查看驱动加载情况

/ # cat /proc/devices
Character devices:1 mem4 /dev/vc/04 tty5 /dev/tty5 /dev/console5 /dev/ptmx7 vcs10 misc13 input29 fb81 video4linux89 i2c90 mtd
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
200 led
207 ttymxc
216 rfcomm
226 drm
249 ttyLP
250 iio
251 watchdog
252 ptp
253 pps
254 rtcBlock devices:1 ramdisk
259 blkext7 loop8 sd31 mtdblock65 sd66 sd67 sd68 sd69 sd70 sd71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
/ #

2、创建设备文件

命令如下所示:

mknod /dev/led c 200 0

查看创建结果:

/ # ls /dev/
autofs              ram11               tty36
bus                 ram12               tty37
console             ram13               tty38
cpu_dma_latency     ram14               tty39
dri                 ram15               tty4
fb0                 ram2                tty40
full                ram3                tty41
fuse                ram4                tty42
hwrng               ram5                tty43
i2c-0               ram6                tty44
i2c-1               ram7                tty45
input               ram8                tty46
kmsg                ram9                tty47
led                 random              tty48
loop-control        rtc0                tty49
loop0               snd                 tty5
loop1               tty                 tty50
loop2               tty0                tty51
loop3               tty1                tty52
loop4               tty10               tty53
loop5               tty11               tty54
loop6               tty12               tty55
loop7               tty13               tty56
mem                 tty14               tty57
memory_bandwidth    tty15               tty58
mmcblk0             tty16               tty59
mmcblk0p1           tty17               tty6
mmcblk1             tty18               tty60
mmcblk1boot0        tty19               tty61
mmcblk1boot1        tty2                tty62
mmcblk1p1           tty20               tty63
mmcblk1p2           tty21               tty7
mmcblk1rpmb         tty22               tty8
mxc_asrc            tty23               tty9
network_latency     tty24               ttymxc0
network_throughput  tty25               ttymxc1
null                tty26               ubi_ctrl
pps0                tty27               urandom
pps1                tty28               vcs
ptmx                tty29               vcs1
ptp0                tty3                vcsa
ptp1                tty30               vcsa1
pts                 tty31               video0
pxp_device          tty32               watchdog
ram0                tty33               watchdog0
ram1                tty34               zero
ram10               tty35               ⚌A-⚌
/ #

通过以上日志可以确定驱动文件加载成功。

3、运行应用程序

点亮 LED,指令如下:

./led_app /dev/led 1

熄灭 LED ,指令如下:

./led_app /dev/led 1

4、测试

执行 app 程序日志如下:

/ # ./led_app /dev/led 1
led open!
led write!
led release!
/ # ./led_app /dev/led 0
led open!
led write!
led release!
/ #

通过观察开发板上 LED 灯状态,可以确定 LED 可以正常打开和关闭。

Linux 驱动开发 五:Linux LED驱动开发相关推荐

  1. NanoPi NEO Air使用九:使用Linux内核自带的LED驱动

    NanoPi NEO Air使用一:介绍 NanoPi NEO Air使用二:固件烧录 NanoPi NEO Air使用三:OverlayFS.CPU温度和频率.wifi.蓝牙.npi-config ...

  2. Linux SD卡驱动开发(五) —— SD 卡驱动分析Core补充篇

    Core层中有两个重要函数 mmc_alloc_host 用于构造host,前面已经学习过,这里不再阐述:另一个就是 mmc_add_host,用于注册host 前面探测函数s3cmci_probe, ...

  3. 如何使用linux系统自带的led驱动

    使用Linux 内核的platform 框架驱动led灯 1.使用menuconfig使能(可跳过,系统默认配置好) 2.设备树节点编写 3.运行测试 我们平时要使用设备驱动的时候,总是需要我们自己去 ...

  4. linux 管道来删除,Linux入门基础(五):Linux管道,重定向,文本处理

    多命令协作:管道及重定向 在Linux中,大多数命令都很简单,每个命令往往只实现一个或几个很简单功能. 我们可以通过将不同功能的命令组合起来一起使用,达到完成某个复杂功能的目的 CLI下几乎所有的命令 ...

  5. 从零开始之驱动发开、linux驱动(五、字符驱动之led驱动改进)

    上一节的代码有两个使用不是很方便问题: 1.写驱动时,每个次设备号都要自己指定.(这样就不能作为移植性强的软件了) 2.引用层使用时不但要手动创建设备节点,还要知道其设备信息和设备号. 我们先来解决第 ...

  6. 嵌入式Linux(二)汇编LED驱动实验

    目的: 并不是系统的学习汇编,而是在linux开发中有时候需要使用汇编置零进行一些初始化的工作. 1. I.MX6ULL的IO初始化流程: 6ULL的IO命名:IOMUXC_SW_MUC_CTL_PA ...

  7. linux设备驱动第五篇:驱动中的并发与竟态

    目录[-] 综述 信号量与互斥锁 Completions 机制 自旋锁 其他的一些选择 不加锁算法 原子变量与位操作 seqlock(顺序锁) 读取-拷贝-更新(RCU) 小结 综述 在上一篇介绍了l ...

  8. rt_thread hc32f460开发五:wdt驱动移植

    看门狗驱动的代码结构和原理 参考STM32的看门狗驱动,RT-Thread中的看门狗驱动主要有两个函数组成wdt_init和wdt_control. 其中wdt_init提供看门狗初始化,主要就是初始 ...

  9. 全志linux led驱动程序,芯灵思Sinlinx A64 linux通过设备树写LED驱动(附参考代码,未测试)...

    #include #include #include #include #include #include #include #include #include #include #include # ...

  10. i.MX 6ULL 驱动开发 六:beep 驱动

    一.原理分析 通过原理图可以确定 beep 连接到 SNVS_TAMPER1 引脚上.根据 beep 原理,当 SNVS_TAMPER1 输出低电平时,beep 鸣叫. 通过数据手册确定 SNVS_T ...

最新文章

  1. 关于loader加载的东西必须是继承sprite
  2. 文件系统的两种文件索引模式extent和blockmap
  3. MySQL:实用 SQL 语句集合
  4. Html:upload
  5. Mysql数据备份恢复及主从同步
  6. xshell 快速复制打开之前用过的ssh
  7. Android AsyncTask 深度理解、简单封装、任务队列分析、自定义线程池
  8. [渝粤教育] 中国地质大学 自动控制原理 复习题 (2)
  9. 计算机文档设置,电脑这样设置快速的共享文件、分享文档!
  10. RN组件使用注意事项
  11. Android开发笔记(一百二十四)自定义相册
  12. 23模式之: 迭代器模式demo
  13. SPendidCRM:给HK的ImageInfoEntryEditView增加一个checkbox,用于判断特殊类型的PODS记录...
  14. php函数strrchr、substr、is_dir、unlink
  15. Quartz定时器实现
  16. Unity 性能优化归纳
  17. 有限域的Lagrange插值分析ZUC流密码S盒的代数结构
  18. Xpose安装 xposed下载出错 http://dl.xposed.info/repo
  19. JavaScript打飞机小游戏
  20. 柳絮飘,往事忆:前言

热门文章

  1. Ubuntu18.04 搜狗输入法安装(史上最好用的输入法安装方法)
  2. 微软小冰的“体面告别”
  3. 在腾讯云上创建CLAA产品
  4. 计算机在摄影应用,计算机X线摄影在临床中的应用
  5. python selenium爬虫豆瓣_使用selenium+requests爬取豆瓣小组讨论列表
  6. 从零开始行人重识别 [中文版]
  7. 用python独立制作Doip刷写ECU工具
  8. NET Namespace(1)
  9. c语言结构体求分数和,C语言 定义一个表示分数的结构体,并求两个分数相加之和...
  10. 6款数据库管理工具推荐,设计简单、功能丰富,还与阿里云兼容哦!