1 Linux内核IOREMAP驱动

在内核驱动的代码中,存在大量代码使用ioremap进行物理地址和虚拟地址映射,使得内核更加容易操作硬件,对比于简单的gpio控制,实际的代码同样是使用了ioremap进行映射封装。

2 ITOP4412示例

2.1 硬件手册

  • 本次测试使用LED3(同理LED2),查看原理图或设备树可知 具体引脚号。

  • 当前使用的为LED3,使用时需要将设备树使用到的节点进行删除。因为设备树会优先占用当前使用的GPIO,需要将GPIO的资源释放。
 leds {compatible = "gpio-leds";led2 {label = "red:system";gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;default-state = "off";linux,default-trigger = "heartbeat";};/*led3 {label = "red:user";gpios = <&gpk1 1 GPIO_ACTIVE_HIGH>;default-state = "off";};*/};
  • 管脚寄存器说明《SEC_Exynos 4412 SCP_Users Manual_Ver.0.10.00_Preliminary.pdf》

  • 查看原理图知道我们操作的是GPK1_1,则只要操控如下寄存器值。

*GPK1CON &= 0xFFFFFF0F;
*GPK1CON |= 0x00000010;
  • 查看原理图知道我们操作的是GPK1_1,则只要操控如下寄存器值。

*GPK1PUD |= 0x000C;
  • 查看原理图知道我们操作的是GPK1_1,则只要操控如下寄存器值。

计算公式稍微复杂,但n就等于gpk1_n
*GPK1DAT &= 0x3D;
*GPK1DAT |= 0x02;

2.2 软件示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <asm/io.h>#define PRINTKERR(fmt, args...)                                         \do {                                                                \printk(KERN_ERR "%-10s: ***cdev_gpio ERROR*** (%s,%d) %s: " fmt,\(in_interrupt() ? "interrupt" : (current)->comm),           \__FILE__, __LINE__, __FUNCTION__ , ##args);                 \} while(0)#define PRINTKINFO(fmt, args...)                                        \do {                                                                \printk(KERN_INFO "cdev_gpio: " fmt , ## args);                  \} while(0)static dev_t cdev_gpio_devno;
static int cdev_gpio_major = 0;
static struct cdev *pcdev_gpio_cdev = NULL;
static struct class *pcdev_gpio_class = NULL;
static struct device *pcdev_gpio_device = NULL;static int cdev_gpio_open (struct inode *inode, struct file *file)
{printk("cdev_gpio_open!!!\n");return 0;
}static ssize_t cdev_gpio_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{printk("cdev_gpio_read!!!\n");return 0;
}static ssize_t cdev_gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{printk("gpioi2c_write!!!\n");return 0;
}static long cdev_gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{printk("cdev_gpio_ioctl!!!\n");return 0;
}static int cdev_gpio_release (struct inode *inode, struct file *file)
{printk("cdev_gpio_release!!!\n");return 0;
}static struct file_operations ioaccess_fops = {.owner          = THIS_MODULE,.open           = cdev_gpio_open,.read           = cdev_gpio_read,.write          = cdev_gpio_write,.unlocked_ioctl = cdev_gpio_ioctl,.release        = cdev_gpio_release,
};volatile unsigned long virt_addr, phys_addr;
volatile unsigned long *GPK1CON, *GPK1DAT, *GPK1PUD;static ssize_t show_cdev_gpio_status(struct device *dev, struct device_attribute *attr, char *buf)
{int status = 0;status = (*GPK1DAT & 0x02) >> 1;sprintf(buf, "%u\n", status);return strlen(buf);
}static ssize_t store_cdev_gpio_status(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{int cfg, c;c = sscanf(buf, "%d", &cfg);if( c >= 1 ){if( cfg > 0 )*GPK1DAT |= 0x02;else*GPK1DAT &= 0x3d;}return count;
}static DEVICE_ATTR(cdev_gpio_status, (S_IRUGO | S_IWUSR), show_cdev_gpio_status, store_cdev_gpio_status);static int __init cdev_gpio_init(void)
{int ret = 0;/* 注册设备号 */ret = alloc_chrdev_region(&cdev_gpio_devno, cdev_gpio_major, 1/*count*/, KBUILD_MODNAME);if (ret) {PRINTKERR("alloc_chrdev_region failed!\n");return ret;}PRINTKINFO("alloc_chrdev_region cdev_gpio_major %d cdev_gpio_minor %d devno %d!\n",MAJOR(cdev_gpio_devno),MINOR(cdev_gpio_devno),cdev_gpio_devno);/* 初始化cdev结构,赋值file_operations结构 */pcdev_gpio_cdev =  cdev_alloc();if (IS_ERR(pcdev_gpio_cdev)) {ret = PTR_ERR(pcdev_gpio_cdev);goto err_unregister_devno;}cdev_init(pcdev_gpio_cdev, &ioaccess_fops);pcdev_gpio_cdev->owner = THIS_MODULE;ret = cdev_add(pcdev_gpio_cdev, cdev_gpio_devno, 1);if (ret) {PRINTKERR("cdev add fail!\n");goto err_unregister_cdev;}/* 创建设备类/sys/class/xxx/ */pcdev_gpio_class = class_create(THIS_MODULE, KBUILD_MODNAME);if (IS_ERR(pcdev_gpio_class)){PRINTKERR("failed to create class %s", "cdev_gpio");ret = PTR_ERR(pcdev_gpio_class);goto err_unregister_cdev;}/* 创建生成设备类节点 */pcdev_gpio_device = device_create(pcdev_gpio_class, NULL, cdev_gpio_devno, NULL, KBUILD_MODNAME);if (IS_ERR(pcdev_gpio_device)){PRINTKERR("failed to create device %s", "cdev_gpio");ret = PTR_ERR(pcdev_gpio_device);goto err_free_class;}/* 创建sysfs接口 */device_create_file(pcdev_gpio_device,&dev_attr_cdev_gpio_status);/* 转换物理地址 */phys_addr = 0x11000060;virt_addr = (unsigned long)ioremap(phys_addr, 0x10);GPK1CON = (unsigned long *)(virt_addr + 0x00);GPK1DAT = (unsigned long *)(virt_addr + 0x04);GPK1PUD = (unsigned long *)(virt_addr + 0x08);*GPK1CON &= 0xFFFFFF0F;*GPK1CON |= 0x00000010;*GPK1PUD |= 0x000C;*GPK1DAT &= 0x3D;printk(KERN_INFO "cdev_gpio_init.\n");return 0;err_free_class:class_destroy(pcdev_gpio_class);
err_unregister_cdev:cdev_del(pcdev_gpio_cdev);
err_unregister_devno:unregister_chrdev_region(cdev_gpio_devno, 1);return ret;
}static void __exit cdev_gpio_exit(void)
{if (pcdev_gpio_class != NULL && pcdev_gpio_device != NULL ) {device_destroy(pcdev_gpio_class, cdev_gpio_devno);class_destroy(pcdev_gpio_class);pcdev_gpio_class = NULL;}cdev_del(pcdev_gpio_cdev);unregister_chrdev_region(cdev_gpio_devno, 1);printk(KERN_INFO "cdev_gpio_exit.\n");
}module_init(cdev_gpio_init);
module_exit(cdev_gpio_exit);MODULE_LICENSE("GPL"); /*指定代码许可证*/
MODULE_AUTHOR("creed"); /*指定作者*/
MODULE_VERSION("V1.00.00"); /*指定代码修订号*/
MODULE_DESCRIPTION("sample_led for modules");

3 获取gpio序号

3.1 debugfs获取gpio序号

Symbol: DEBUG_FS [=y]Prompt: Debug FilesystemDefined at lib/Kconfig.debug:77Depends on: SYSFS     Location:-> Kernel configuration-> Kernel hacking
mount -t debugfs none /sys/kernel/debug

获取后从如下的gpio打印中可以看出GPIO号,并且是否被占用

[root@itop4412 /]# cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-7, parent: platform/11400000.pinctrl, gpa0:gpiochip1: GPIOs 8-13, parent: platform/11400000.pinctrl, gpa1:gpiochip2: GPIOs 14-21, parent: platform/11400000.pinctrl, gpb:gpiochip3: GPIOs 22-26, parent: platform/11400000.pinctrl, gpc0:gpio-23  (                    |ehci_vbus_gpio      ) out hi    gpiochip4: GPIOs 27-31, parent: platform/11400000.pinctrl, gpc1:

3.2 dmesg的GPIO加载

dmesg | grep gpio

从如下打印可以看出与方法一致

[root@itop4412 /]# dmesg | grep gpio
[    0.431932] gpio gpiochip0: (gpa0): added GPIO chardev (254:0)
[    0.431947] gpiochip_setup_dev: registered GPIOs 0 to 7 on device: gpiochip0 (gpa0)
[    0.432223] gpio gpiochip1: (gpa1): added GPIO chardev (254:1)
[    0.432234] gpiochip_setup_dev: registered GPIOs 8 to 13 on device: gpiochip1 (gpa1)
[    0.432510] gpio gpiochip2: (gpb): added GPIO chardev (254:2)
[    0.432520] gpiochip_setup_dev: registered GPIOs 14 to 21 on device: gpiochip2 (gpb)
[    0.432779] gpio gpiochip3: (gpc0): added GPIO chardev (254:3)
[    0.432790] gpiochip_setup_dev: registered GPIOs 22 to 26 on device: gpiochip3 (gpc0)
[    0.433054] gpio gpiochip4: (gpc1): added GPIO chardev (254:4)
[    0.433065] gpiochip_setup_dev: registered GPIOs 27 to 31 on device: gpiochip4 (gpc1)
[    0.433324] gpio gpiochip5: (gpd0): added GPIO chardev (254:5)
[    0.433335] gpiochip_setup_dev: registered GPIOs 32 to 35 on device: gpiochip5 (gpd0)
[    0.433599] gpio gpiochip6: (gpd1): added GPIO chardev (254:6)
[    0.433610] gpiochip_setup_dev: registered GPIOs 36 to 39 on device: gpiochip6 (gpd1)
[    0.433869] gpio gpiochip7: (gpf0): added GPIO chardev (254:7)
[    0.433880] gpiochip_setup_dev: registered GPIOs 40 to 47 on device: gpiochip7 (gpf0)
[    0.434146] gpio gpiochip8: (gpf1): added GPIO chardev (254:8)
[    0.434157] gpiochip_setup_dev: registered GPIOs 48 to 55 on device: gpiochip8 (gpf1)

3.3 内核gpio操作示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>#define PRINTKERR(fmt, args...)                                         \do {                                                                \printk(KERN_ERR "%-10s: ***gpio ERROR*** (%s,%d) %s: " fmt,\(in_interrupt() ? "interrupt" : (current)->comm),           \__FILE__, __LINE__, __FUNCTION__ , ##args);                 \} while(0)#define PRINTKINFO(fmt, args...)                                        \do {                                                                \printk(KERN_INFO "gpio: " fmt , ## args);                  \} while(0)/*
*[    0.437965] gpiochip_setup_dev: registered GPIOs 90 to 96 on device: gpiochip14 (gpk1)
*/
#define GPK1_1_GPIO_NUMBER 91static void platform_device_gpio_release(struct device *dev)
{printk("platform_device_gpio_release \n");return;
}static struct platform_device platform_device_gpio = {.name = KBUILD_MODNAME, /* should be the same name as driver_name */.id = -1,.dev = {.release = platform_device_gpio_release,},
};static ssize_t show_cdev_gpio_status(struct device *dev, struct device_attribute *attr, char *buf)
{printk("show_cdev_gpio_status!!!\n");return 0;
}static ssize_t store_cdev_gpio_status(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{int cfg, c;c = sscanf(buf, "%d", &cfg);if( c >= 1 ){if( cfg > 0 )gpio_set_value(GPK1_1_GPIO_NUMBER, 0);elsegpio_set_value(GPK1_1_GPIO_NUMBER, 1);}return count;
}static DEVICE_ATTR(cdev_gpio_status, (S_IRUGO | S_IWUSR), show_cdev_gpio_status, store_cdev_gpio_status);static struct attribute *dev_attrs[] = {&dev_attr_cdev_gpio_status.attr,NULL
};struct attribute_group dev_attr_group = {.attrs = dev_attrs,
};static int __init platform_device_gpio_init(void)
{int ret = 0;/* 检测GPIO是否合法 */if (!gpio_is_valid(GPK1_1_GPIO_NUMBER)) {printk(KERN_ERR "gpio is unavailable.\n");return -1;}/* 申请GPIO口,参数一:申请的端口号;参数二:申请后的GPIO标识 */ret = gpio_request(GPK1_1_GPIO_NUMBER, "gpio_led");if ( ret < 0 ) {printk(KERN_ERR "gpio request error.\n");return -1;}/* 设置GPIO方向,具体点亮需要结合硬件 */ret= gpio_direction_output(GPK1_1_GPIO_NUMBER, 1);if ( ret < 0 ) {PRINTKERR("gpio direct output error");goto err_free_gpio;}/* 创建sysfs接口 */ret = platform_device_register(&platform_device_gpio);if ( ret < 0 ) {PRINTKERR("failed to platform_device_register %s", "device_gpio");goto err_free_gpio;}if (sysfs_create_group(&platform_device_gpio.dev.kobj, &dev_attr_group)) {PRINTKERR("can't create sysfs files\n");goto err_unregister_platform_device;}printk(KERN_INFO "platform_device_gpio_init.\n");return 0;err_unregister_platform_device:platform_device_unregister(&platform_device_gpio);
err_free_gpio:gpio_free(GPK1_1_GPIO_NUMBER);return ret;
}static void __exit platform_device_gpio_exit(void)
{platform_device_unregister(&platform_device_gpio);printk(KERN_INFO "platform_device_gpio_exit.\n");
}module_init(platform_device_gpio_init);
module_exit(platform_device_gpio_exit);MODULE_LICENSE("GPL"); /*指定代码许可证*/
MODULE_AUTHOR("creed"); /*指定作者*/
MODULE_VERSION("V1.00.00"); /*指定代码修订号*/
MODULE_DESCRIPTION(" gpio control for modules");

4 sysfs接口控制gpio

确认是否开启了sysfs方式控制gpio,查看系统是否有/sys/class/gpio这个文件夹,假如没有进入内核则Device Drivers-> GPIO Support

#system ready set gpioe7 high level
echo 39 > /sys/class/gpio/exportc
echo out > /sys/class/gpio/gpio39/direction
echo 1 > /sys/class/gpio/gpio39/value

关于的/sys/class/gpio/用法

  • export:用于通知系统需要导出控制的GPIO引脚编号
  • unexport:用于通知系统取消导出c
  • gpiochipx:目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号base,寄存器名称,引脚总数 导出一个引脚的操作
/ # ls /sys/class/gpio/gpio39/
active_low  device/     direction   edge        subsystem/  uevent      value
  • direction:in、out、high、low,high/low同时设置方向为输出,并将value设置为相应的1/0
  • value:0/1

5 ioremap解析

arch\arm\include\asm\io.h

void __iomem *ioremap(resource_size_t res_cookie, size_t size);

在开发过程中ioremap操作过程充斥格式各样的读写函数,对比每一种函数怎么使用显得尤为重要。

void __iomem *gpio_io_base;

include/linux/compile.h

# define __iomem __attribute__((noderef, address_space(2)))
v: 0 内核空间
v: 1 用户空间
v: 2 io存储空间
v: 3 cpu空间

5.1 读函数

unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
unsigned char readl (unsigned int addr);

readl() 从内存映射的 I/O 空间读取数据,readl 从 I/O 读取 32 位数据 ( 4 字节 )

5.2 写函数

unsigned int iowrite8(u8 value, void *addr);
unsigned int iowrite16(u8 value, void *addr);
unsigned int iowrite32(u8 value, void *addr);
void writel (unsigned char data , unsigned short addr);

writel() 往内存映射的 I/O 空间上写数据,wirtel() I/O 上写入 32 位数据 (4字节)

5.3 内核gpio模拟I2C

#define SET_SCL0_1     gpio_set_value(AST1500_GPJ3,1)
#define SET_SDA0_1     gpio_set_value(AST1500_GPJ2,1)#define SET_SCL0_0     gpio_set_value(AST1500_GPJ3,0)
#define SET_SDA0_0     gpio_set_value(AST1500_GPJ2,0)#define GET_SDA0       gpio_get_value(AST1500_GPJ2)#define SCL0_IN        gpio_cfg(AST1500_GPJ3,AST1500_GPIO_INPUT)
#define SCL0_OUT       gpio_cfg(AST1500_GPJ3,AST1500_GPIO_OUTPUT)#define SDA0_IN        gpio_cfg(AST1500_GPJ2,AST1500_GPIO_INPUT)
#define SDA0_OUT       gpio_cfg(AST1500_GPJ2,AST1500_GPIO_OUTPUT)void I2CDelay(unsigned short delay)
{unsigned short j = 0;for(j=0;j<delay;j++);return;
}/*
PB0-1: index 0 I2C 0:SDA, 1:SCL
PB2-3: index 1 I2C 2:SDA, 3:SCL
PB4-5: index 2 I2C 4:SDA, 5:SCL
*/void ManReleaseScl(unsigned char index)
{switch(index){case 0:SCL0_IN;break;}}void ManDriveScl(unsigned char index)
{switch(index){case 0:SCL0_OUT;break;}}void ManReleaseSda(unsigned char index)
{switch(index){case 0:SDA0_IN;break;}}void ManDriveSda(unsigned char index)
{switch(index){case 0:SDA0_OUT;break;}}void ManSclHigh(unsigned char index)
{switch(index){case 0:SET_SCL0_1;break;}
}void ManSclLow(unsigned char index)
{switch(index){case 0:SET_SCL0_0;break;}
}void ManSdaHigh(unsigned char index)
{switch(index){case 0:SET_SDA0_1;break;}
}void ManSdaLow(unsigned char index)
{switch(index){case 0:SET_SDA0_0;break;}
}unsigned char GetSdaValue(unsigned char index)
{unsigned int value = 0;switch(index){case 0:value = GET_SDA0;break;}if(value)value = 1;elsevalue = 0;return value;
}

5.4 内核writel/readl模拟I2C

#define IO_ADDRESS_GPIO(pin)        ( (void __iomem*)AST1500_GPIO_CON(pin) )
#define IO_ADDRESS_GPIO_OFFSET(pin) ( AST1500_GPIO_OFFSET(pin) )
#define SCL AST1500_GPJ3
#define SDA AST1500_GPJ2#define DELAY(us)      udelay(us)     //time_delay_us(us)static spinlock_t  gpioi2c_lock;static void  i2c_clr(unsigned char whichline)
{unsigned long regvalue;unsigned long mask;unsigned int direction;unsigned long dat;unsigned long offs = IO_ADDRESS_GPIO(SCL);regvalue = __raw_readl(IO_ADDRESS_GPIO(SCL));mask = 1 << IO_ADDRESS_GPIO_OFFSET(SCL);direction = 1 << IO_ADDRESS_GPIO_OFFSET(SCL);regvalue &= ~mask;regvalue |= direction;__raw_writel(regvalue, IO_ADDRESS_GPIO(SCL));//__raw_writel(0, IO_ADDRESS_GPIO(SCL));dat = __raw_readl(IO_ADDRESS_GPIO(SCL));dat &= ~(1 << offs);dat |= 0 << offs;__raw_writel(dat, IO_ADDRESS_GPIO(SCL));#if 0if(whichline == SCL){regvalue = __raw_readl(IO_ADDRESS_GPIO(SCL));return;}else if(whichline == SDA){regvalue = __raw_readl(IO_ADDRESS_GPIO(SDA));return;}else if(whichline == (SDA|SCL)){return;}else{cprintk("Error input.\n");return;}#endif
}

6 参考资料

操作硬件IO内存
不同的CPU架构提供不同的IO和内存管理
CPU如何通过MMU访问到物理地址

7 资源链接

https://download.csdn.net/download/weixin_44143206/85032052

Linux内核IOREMAP驱动相关推荐

  1. Linux内核网络设备驱动

    本文首先从宏观上介绍数据包的接收过程,然后详细介绍了Linux网络设备驱动的工作过程,最后介绍网卡监控与调优,包括网络数据包总数.丢包.错包数量的相关统计. 1. 接收数据包过程概述 介绍数据包收包过 ...

  2. Android系统 linux内核按键驱动开发

    Android系统 linux内核按键驱动开发 前言 刚入门的小白,在csdn的帮助下完成了第一个按键驱动,特写此文记录学习并分享给有需要的人. 1.修改设备树.dts 我是用的开发板是rp-rk32 ...

  3. linux内核led驱动开发,从Linux内核LED驱动来理解字符设备驱动开发流程

    目录 博客说明 开发环境 1. Linux字符设备驱动的组成 1.1 字符设备驱动模块加载与卸载函数 1.2 字符设备驱动的file_operations 结构体中的成员函数 2. 字符设备驱动--设 ...

  4. 【嵌入式环境下linux内核及驱动学习笔记-(16)linux总线、设备、驱动模型之input框架】

    目录 1.Linux内核输入子系统概念导入 1.1 输入设备工作机制 1.2 运行框架 1.3 分层思想 2.驱动开发步骤 2.1 在init()或probe()函数中 2.2 在exit()或rem ...

  5. 【嵌入式环境下linux内核及驱动学习笔记-(15-1)例程】

    目录 1.在APP直接调用标准文件IO操作I2C(针对学习笔记-15的15.3节) 1.1 mail.c 1.2 mpu6050.h 1.3 mpu6050.c 1.4 Makefile 2.以外称i ...

  6. 深入讲解Linux内核网络设备驱动(图例解析)

    1. 接收数据包过程概述 介绍数据包收包过程,有助于我们了解Linux内核网络设备在数据收包过程中的位置,下面从宏观的角度介绍数据包从被网卡接收到进入 socket 接收队列的整个过程: 加载网卡驱动 ...

  7. 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的I2C驱动架构分析

    PowerPC + Linux2.6.25平台下的I2C驱动架构分析 Sailor_forever  sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人b ...

  8. 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的SPI驱动架构分析

    PowerPC + Linux2.6.25平台下的SPI驱动架构分析 Sailor_forever  sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人b ...

  9. 面经——Linux内核及驱动

    VFS https://www.linuxprobe.com/linux-vfs.html 三种设备类型 字符设备:字符设备是能够以字节流形式被访问的设备,字符设备驱动程序通常至少实现open,clo ...

最新文章

  1. ​Xamarin iOS教程之自定义视图
  2. 麦子mysql_[数据库]MySQL基础 (麦子学员 php 第二阶段)
  3. 大数——大数阶乘(hdu1042)
  4. 分析java程序的命令总结jps,jstack
  5. persevere的用法_persevere的用法是什么
  6. 科技经济调整td-scdma中国
  7. Pearson 相关分析 理论与应用
  8. google地图距离算法_谷歌地图计算两个坐标点的距离函数
  9. android ps4 远程,手机上玩PS4游戏!Remote Play扩大至所有Android设备
  10. buu刷题日记 asis2016_b00ks
  11. 数据库原理与应用实验3--(数据库的简单查询和连接查询)
  12. web前端开发的6个福利网站
  13. sql简介香气和sql简介_香气和SQL简介
  14. Golang Tools
  15. HTML笔记及案例(全)
  16. Mogafx澳元技术性预测
  17. 阿里巴巴 Excel工具easyExcel
  18. ERP实施工程师必备知识体系及职业操守
  19. java word 加密_Java 加密Word文档
  20. 优化算法之梯度下降法、牛顿法、拟牛顿法和拉格朗日乘数法

热门文章

  1. 《明朝那些事儿》和《淘宝技术这十年》
  2. 读取二进制文件(后缀.bin),解析二进制文件,并入库
  3. 22 个苹果风格的 Web 设计
  4. The current identity (NT AUTHORITY\NETWORK SERVICE) does not have write access
  5. 什么是判别式模型?什么是生成式模型?
  6. Cocos Creator 重力球游戏制作教程
  7. openflow简介
  8. Java基础-配置开发环境-安装JDK
  9. ios图片添加文字或者水印
  10. 【ISO14229_UDS_0x27服务详解】