Linux驱动:LCD驱动框架分析
一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了。LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂一点点,真的就是一点点,难点在对LCD硬件的配置上。
开发平台:TQ210,S5PV210处理器
内核版本:linux-3.10.46
LCD型号:AT070TN92,7英寸,TFT屏,分辨率800x480x3(RGB),24位真彩色
一、框架分析
上图说明:①内核装载LCD驱动模块:设置并注册fb_info结构,初始化LCD硬件。②APP打开LCD设备,获取设备文件,根据设备文件进行读写显存。③在内核中,根据主设备号和次设备号定位一个fb_info结构,如果应用层的系统调用是读操作则调用fb_ops中对应的操作函数,写操作也是一样。
主要数据结构分析:
struct fb_info {
int node; //用作次设备号索引
int flags;
struct mutex lock; //用于open/release/ioctl函数的锁
struct fb_var_screeninfo var; //可变参数,重点
struct fb_fix_screeninfo fix; //固定参数,重点
struct fb_monspecs monspecs; //显示器标准
struct work_struct queue; //帧缓冲区队列
struct fb_pixmap pixmap; //图像硬件映射
struct fb_pixmap sprite; //光标硬件映射
struct fb_cmap cmap; //当前颜色表
struct list_head modelist; //模式链表
struct fb_videomode *mode; //当前video模式 char __iomem *screen_base; //显存基地址
unsigned long screen_size; //显存大小
void *pseudo_palette; //伪16色颜色表
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; //硬件状态,如挂起
void *fbcon_par; //用作私有数据区
void *par; //info->par指向了额外多申请内存空间的首地址
};
fb_info
struct fb_ops {
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos); /* 检测可变参数,并调整到支持的值 */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info); /* 根据 info->var 设置 video 模式 */
int (*fb_set_par)(struct fb_info *info); /* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);
/* set color registers in batch */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/* blank display */
int (*fb_blank)(int blank, struct fb_info *info);
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info); /* Draws a rectangle */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* Copy data from area to another */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* Draws a image to the display */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); ...... /* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma); ......
};
fb_ops
主要操作代码分析:
fb_open
{
int fbidx = iminor(inode); //获取次设备号
struct fb_info *info;
info = get_fb_info(fbidx);
struct fb_info *fb_info;
fb_info = registered_fb[fbidx];//根据次设备号从已注册的fb_info数组中获取响应的结构
return fb_info;
......
/*
* 从registered_fb[]数组项里找到fb_info结构体后,将其保存到
* struct file结构中的私有信息成员,难道这是为了以后在某些情况方便找到并调用??先放着...
* 回过来发现:这样做是为了验证在read、write、ioctl等系统调用中获得的fb_info结构和open获得的是否一样
*/
file->private_data = info;
//info->fbops->fb_open无定义,这是值得思考的问题!
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,);
if (res)
module_put(info->fbops->owner);
}
......
}
fb_open
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct fb_info *info = file_fb_info(file);
struct inode *inode = file_inode(file);
int fbidx = iminor(inode);
//也是根据次设备号来获取fb_info结构
struct fb_info *info = registered_fb[fbidx]; if (info != file->private_data)
info = NULL;
return info;
//无定义
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
//获得显存的大小
total_size = info->screen_size;
//如果应用层要读的数据count比实际最大的显存还要大,修改count值为最大显存值
if (count >= total_size)
count = total_size;
//分配显存,最大只能是一页PAGE_SIZE=4KB
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
//要读的源地址:显存虚拟基地址+偏移
src = (u8 __iomem *) (info->screen_base + p);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
//读的目的地址
dst = buffer;
//读操作:拷贝数据
fb_memcpy_fromfb(dst, src, c);
dst += c;
src += c; if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer); //释放buffer,只起到临时中转站的作用
}
fb_read
static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct fb_info *info = file_fb_info(file); //获取fb_info结构
/************************************************************
函数跟进分析:
static struct fb_info *file_fb_info(struct file *file)
{
struct inode *inode = file_inode(file);
int fbidx = iminor(inode); //获取次设备号
struct fb_info *info = registered_fb[fbidx]; //根据次设备号获取相应的fb_info结构 if (info != file->private_data)
info = NULL;
return info; //返回fb_info结构
}
************************************************************/
u8 *buffer, *src;
u8 __iomem *dst;
int c, cnt = , err = ;
unsigned long total_size;
//获取fb_info失败或者fb_info结构中没有设置显存基址,返回
if (!info || !info->screen_base)
return -ENODEV; if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
//如果帧缓冲操作函数结构中有重定义fb_write函数,优先使用!实际上没有。
if (info->fbops->fb_write)
return info->fbops->fb_write(info, buf, count, ppos);
//获取显存大小
total_size = info->screen_size; if (total_size == )
total_size = info->fix.smem_len;
//如果写偏移位置p比整个显存还要大,出错返回。
if (p > total_size)
return -EFBIG; if (count > total_size) {
err = -EFBIG;
count = total_size;
} if (count + p > total_size) {
if (!err)
err = -ENOSPC; count = total_size - p;
}
//内核空间分配临时帧缓冲区
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
if (!buffer)
return -ENOMEM;
//计算写目的地址(虚拟地址:内核空间中能够操作的也就是虚拟地址)
dst = (u8 __iomem *) (info->screen_base + p); if (info->fbops->fb_sync)
info->fbops->fb_sync(info); while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
//源地址
src = buffer; if (copy_from_user(src, buf, c)) {
err = -EFAULT;
break;
}
// 从内存buffer拷贝数据到帧缓冲区
fb_memcpy_tofb(dst, src, c);
dst += c;
src += c;
*ppos += c;
buf += c;
cnt += c;
count -= c;
} kfree(buffer);
return (cnt) ? cnt : err;
}
fb_write
/*
* 函数功能:将内核空间分配的物理显存空间映射到用户空间中
* 用户空间就能访问这段内存空间了
*/
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file);
struct fb_ops *fb;
unsigned long mmio_pgoff;
unsigned long start;
u32 len; if (!info)
return -ENODEV;
fb = info->fbops;
if (!fb)
return -ENODEV;
mutex_lock(&info->mm_lock);
//如果fb_info->fbops->fb_mmap存在就调用该函数,实际中没有!
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
/*
* fb缓冲内存的开始位置(物理地址)
* info->fix.smem_start这个地址是在哪里被设置的?
* 在驱动程序xxx_lcd_init()函数中:
* clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
* dma_alloc_writecombine函数返回的是内核虚拟起始地址,同时第3个参数fix.smem_start会被设置成对应的物理起始地址。
* 内核中操作这个分配的空间只能操作虚拟的地址空间!!!
* dma_alloc_writecombine函数的调用只是把物理显存映射到内核空间,并没有映射到用户空间,因此用户在操作物理显存之前要先把
* 物理显存空间映射到用户可见的用户空间中来,这就是该函数的意义所在。
*/
start = info->fix.smem_start;
//帧缓冲长度
len = info->fix.smem_len;
mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
if (vma->vm_pgoff >= mmio_pgoff) {
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
} vma->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start;
len = info->fix.mmio_len;
}
mutex_unlock(&info->mm_lock); vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
fb_pgprotect(file, vma, start);
//映射物理内存到用户空间虚拟地址
return vm_iomap_memory(vma, start, len);
}
fb_mmap
问题思考:
问1.什么叫帧缓冲区,他有哪些特性指标?
答1.对于应用层来说,显示图像到LCD设备就相当于往“一块内存”中写入数据,获取LCD设备上的图像就相当于拷贝“这块内存”中的数据。因此,LCD就和“一块内存”一样,专业一点术语叫帧缓冲区,它和普通的内存不太一样,除了可以“读写”操作之外还可以进行其他操作和功能设置,特性指标就是LCD的特性指标。在内核中,一个LCD显示器就相当于一个帧缓冲设备,对应一个fb_info结构。
问2.为什么要通过 registered_fb[] 数组来找到对应的 fb_info 结构体?
答2.通过对上边这几个函数的剖析发现,不管是fb_read、fb_write、fb_ioctl、fb_mmap系统调用,都是通过次设备号在已注册的fb_info结构数组中找到匹配的那一个结构之后,判断其中的fbops结构中的操作函数是否有定义,有的话就优先调用该函数,没有就使用往下的方案策略。这样的好处就是多个相同的LCD设备可以使用同一套代码,减少代码的重复性,同时对于需要特殊定义的函数又可以方便实现重定义。
问3.这个数组在哪里被注册?
答3.在register_framebuffer()函数中被注册 register_framebuffer(struct fb_info *fb_info) ret = do_register_framebuffer(fb_info); ...... registered_fb[i] = fb_info; ......
问4.fb_mmap()函数在什么场合使用?
答4.在用户空间中通过mmap()函数来进行系统调用,该函数执行成功返回的是指向被映射的帧缓冲区的指针,这样用户直接可以通过该指针来读写缓冲区。
问5.在用户程序中调用write函数和直接使用mmap函数返回的fbp指针有什么不一样?
答5.用户空间使用fbp指针操作的地址是用户空间和物理显存空间直接映射的关系,而使用write是将用户中的数据拷贝到内核空间,然后再将这些数据写到内核中已映射的虚拟地址空间中;write是操作整个fb,而fbp只操作一个像素点。
二、驱动代码编写
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/workqueue.h> #include <asm/io.h>
#include <asm/div64.h>
#include <asm/uaccess.h> #include <asm/mach/map.h>
#include <mach/regs-gpio.h>
#include <linux/fb.h> #define VSPW 9 //
#define VBPD 13 //
#define LINEVAL 479
#define VFPD 21 // #define HSPW 19 //
#define HBPD 25 //
#define HOZVAL 799
#define HFPD 209 // #define LeftTopX 0
#define LeftTopY 0
#define RightBotX 799
#define RightBotY 479 static struct fb_info *clb_fbinfo; /* LCD GPIO Pins */
static long unsigned long *gpf0con;
static long unsigned long *gpf1con;
static long unsigned long *gpf2con;
static long unsigned long *gpf3con;
static long unsigned long *gpd0con;
static long unsigned long *gpd0dat;
static long unsigned long *display_control; /* LCD Controler Pins */
struct s5pv210_lcd_regs{
volatile unsigned long vidcon0;
volatile unsigned long vidcon1;
volatile unsigned long vidcon2;
volatile unsigned long vidcon3; volatile unsigned long vidtcon0;
volatile unsigned long vidtcon1;
volatile unsigned long vidtcon2;
volatile unsigned long vidtcon3; volatile unsigned long wincon0;
volatile unsigned long wincon1;
volatile unsigned long wincon2;
volatile unsigned long wincon3;
volatile unsigned long wincon4; volatile unsigned long shadowcon;
volatile unsigned long reserve1[]; volatile unsigned long vidosd0a;
volatile unsigned long vidosd0b;
volatile unsigned long vidosd0c;
}; struct clk *lcd_clk;
static struct s5pv210_lcd_regs *lcd_regs; static long unsigned long *vidw00add0b0;
static long unsigned long *vidw00add1b0; static u32 pseudo_palette[]; /* from pxafb.c */
static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= - bf->length;
return chan << bf->offset;
} static int clb210_lcdfb_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
unsigned int val; if (regno > )
return ; /* 用red,green,blue三原色构造出val */
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue); pseudo_palette[regno] = val; return ;
} //帧缓冲操作函数
static struct fb_ops clb210_lcdfb_ops =
{
.owner = THIS_MODULE,
.fb_setcolreg = clb210_lcdfb_setcolreg, //设置color寄存器和调色板
//下面这3个函数是通用的
.fb_fillrect = cfb_fillrect, //画一个矩形
.fb_copyarea = cfb_copyarea, //数据拷贝
.fb_imageblit = cfb_imageblit, //图像填充
}; static int __init clb210_lcd_init(void)
{
/* 1.分配一个fb_info */
clb_fbinfo = framebuffer_alloc( , NULL); /* 2. 设置 */
/* 2.1 设置固定的参数 */
strcpy(clb_fbinfo->fix.id, "clb210_lcd");
clb_fbinfo->fix.smem_len = * * /;
clb_fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
clb_fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
clb_fbinfo->fix.line_length = * /; /* 2.2 设置可变的参数 */
clb_fbinfo->var.xres = ;
clb_fbinfo->var.yres = ;
clb_fbinfo->var.xres_virtual = ;
clb_fbinfo->var.yres_virtual = ;
clb_fbinfo->var.bits_per_pixel = ; /*RGB:888*/
clb_fbinfo->var.red.offset = ;
clb_fbinfo->var.red.length = ; clb_fbinfo->var.green.offset = ;
clb_fbinfo->var.green.length = ; clb_fbinfo->var.blue.offset = ;
clb_fbinfo->var.blue.length = ; clb_fbinfo->var.activate = FB_ACTIVATE_NOW ; /* 2.3 设置操作函数 */
clb_fbinfo->fbops = &clb210_lcdfb_ops; /* 2.4 其他的设置 */
/* 2.4.1 设置显存的大小 */
clb_fbinfo->screen_size = * * /; /* 2.4.2 设置调色板 */
clb_fbinfo->pseudo_palette = pseudo_palette; /* 2.4.3 设置显存的虚拟起始地址 */
clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,
clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL); /* 3. 硬件相关的操作 */
/* 3.1 获取lcd时钟,使能时钟 */
lcd_clk = clk_get(NULL, "lcd");
if (!lcd_clk || IS_ERR(lcd_clk)) {
printk(KERN_INFO "failed to get lcd clock source\n");
}
clk_enable(lcd_clk); /* 3.2 配置GPIO用于LCD */
gpf0con = ioremap(0xE0200120, );
gpf1con = ioremap(0xE0200140, );
gpf2con = ioremap(0xE0200160, );
gpf3con = ioremap(0xE0200180, );
gpd0con = ioremap(0xE02000A0, );
gpd0dat = ioremap(0xE02000A4, ); gpd0con = ioremap(0xE02000A0, );
gpd0dat = ioremap(0xE02000A4, ); vidcon0 = ioremap(0xF8000000, );
vidcon1 = ioremap(0xF8000004, );
vidtcon0 = ioremap(0xF8000010, );
vidtcon1 = ioremap(0xF8000014, );
vidtcon2 = ioremap(0xF8000018, );
wincon0 = ioremap(0xF8000020, );
vidosd0a = ioremap(0xF8000040, );
vidosd0b = ioremap(0xF8000044, );
vidosd0c = ioremap(0xF8000048, );
vidw00add0b0 = ioremap(0xF80000A0, );
vidw00add1b0 = ioremap(0xF80000D0, );
shodowcon = ioremap(0xF8000034, ); display_control = ioremap(0xe0107008, ); /* 设置相关GPIO引脚用于LCD */
*gpf0con = 0x22222222;
*gpf1con = 0x22222222;
*gpf2con = 0x22222222;
*gpf3con = 0x22222222; /* 使能LCD本身 */
*gpd0con |= <<;
*gpd0dat |= <<; /* 显示路径的选择, 0b10: RGB=FIMD I80=FIMD ITU=FIMD */
*display_control = <<; /* 3.3 映射LCD控制器对应寄存器 */
lcd_regs = ioremap(0xF8000000, sizeof(struct s5pv210_lcd_regs));
vidw00add0b0 = ioremap(0xF80000A0, );
vidw00add1b0 = ioremap(0xF80000D0, ); lcd_regs->vidcon0 &= ~((<<) | (<<) | (0xff<<) | (<<));
lcd_regs->vidcon0 |= ((<<) | (<<) ); lcd_regs->vidcon1 &= ~(<<); /* 在vclk的下降沿获取数据 */
lcd_regs->vidcon1 |= ((<<) | (<<)); /* HSYNC极性反转, VSYNC极性反转 */ lcd_regs->vidtcon0 = (VBPD << ) | (VFPD << ) | (VSPW << );
lcd_regs->vidtcon1 = (HBPD << ) | (HFPD << ) | (HSPW << );
lcd_regs->vidtcon2 = (LINEVAL << ) | (HOZVAL << );
lcd_regs->wincon0 &= ~(0xf << );
lcd_regs->wincon0 |= (0xB<<)|(<<);
lcd_regs->vidosd0a = (LeftTopX<<) | (LeftTopY << );
lcd_regs->vidosd0b = (RightBotX<<) | (RightBotY << );
lcd_regs->vidosd0c = (LINEVAL + ) * (HOZVAL + ); *vidw00add0b0 = clb_fbinfo->fix.smem_start;
*vidw00add1b0 = clb_fbinfo->fix.smem_start + clb_fbinfo->fix.smem_len; lcd_regs->shadowcon = 0x1; /* 使能通道0 */
lcd_regs->vidcon0 |= 0x3; /* 开启总控制器 */
lcd_regs->wincon0 |= ; /* 开启窗口0 */ /*4.注册*/
register_framebuffer(clb_fbinfo); return ;
}
static void __exit clb210_lcd_exit(void)
{
unregister_framebuffer(clb_fbinfo);
dma_free_writecombine(NULL, clb_fbinfo->fix.smem_len, clb_fbinfo->screen_base, clb_fbinfo->fix.smem_start);
iounmap(gpf0con);
iounmap(gpf1con);
iounmap(gpf2con);
iounmap(gpf3con);
iounmap(gpd0con);
iounmap(gpd0dat); iounmap(display_control);
iounmap(lcd_regs);
iounmap(vidw00add0b0);
iounmap(vidw00add1b0);
framebuffer_release(clb_fbinfo);
} module_init(clb210_lcd_init);
module_exit(clb210_lcd_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("clb");
MODULE_DESCRIPTION("Lcd driver for clb210 board");
lcd_drv.c
这份代码没有基于platform设备驱动来编写,在内核源码中的demo就是基于platform驱动模型来搭建的,主要内容其实一样。
Linux驱动:LCD驱动框架分析的更多相关文章
- Linux的LCD驱动分析及移植
测试平台 宿主机平台:Ubuntu 12.04.4 LTS 目标机:Easy-ARM IMX283 目标机内核:Linux 2.6.35.3 LCD驱动分析 LCD屏的驱动总体上分成两块,一块是GUI ...
- Linux学习: LCD驱动
一.LCD驱动框架: 1.分配一个fb_info结构体:s3c_lcd = framebuffer_alloc(0,NULL); 2.设置fb_info(s3c_lcd): ID.固定参数.可变参数. ...
- 10. LCD驱动程序 ——框架分析
引言: 由LCD的硬件原理及操作(可参看韦哥博客:第017课 LCD原理详解及裸机程序分析) 我们知道只要LCD控制器的相关寄存器正确配置好,就可以在LCD面板上显示framebuffer中的内容. ...
- Linux摄像头驱动学习之:(一)V4L2_框架分析
这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...
- Linux USB驱动框架分析(2)【转】
转自:http://blog.chinaunix.net/uid-23046336-id-3243543.html 看了http://blog.chinaunix.net/uid-11848011 ...
- Linux USB驱动框架分析 【转】
转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...
- linux驱动基础系列--linux spi驱动框架分析
前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...
- linux驱动基础系列--linux spi驱动框架分析(续)
前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...
- Linux下USB驱动框架分析【转】
转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...
随机推荐
- Java动态代理探讨
代理模式: 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.通过代理模式,可以延迟创建对象, ...
- Exce行列变色
- select自定义下拉选择图标
闲言少叙: 上CSS: appearance: none; -moz-appearance: none; -webkit-appearance: none; cursor: pointer; back ...
- 20170906工作日记--volley源码的相关方法细节学习
1. 在StringRequest类中的75行--new String();使用方法 /** * 工作线程将会调用这个方法 * @param response Response from the ne ...
- express session
一.什么是session? 最近在学习node.js 的express框架,接触到了关于session方面的内容.翻阅了一些的博客,学到了不少东西,发现一篇博文讲的很好,概念内容摘抄如下: Sessi ...
- TempDB--临时表的缓存
--========================================================================== 在博客园看到一篇文章<SQLServer ...
- asp.net—工厂模式
一.什么是工厂模式 定义:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类. 二.怎么使用工厂模式 首先模拟一个场景:有一个汽车工厂, 可以日本车.美国车.中国车... 这个场景怎么用工厂 ...
- C#——调用C++的DLL 数据类型转换
/C++中的DLL函数原型为 //extern "C" __declspec(dllexport) bool 方法名一(const char* 变量名1, unsig ...
- Ruby for Sketchup 贪吃蛇演示源码(naive_snake)
sketchup是非常简单易用的三维建模软件,可以利用ruby 做二次开发, api文档 http://www.rbc321.cn/api 今天在su中做了一款小游戏 贪吃蛇,说一下步骤 展示 主要思 ...
- Algorithm 用各种语言写出n!的算法
写出n!的算法 C# 递归方式: class Program { static void Main(string[] args) { Console.WriteLine("请输入一个数!&q ...