Linux块设备驱动(一) _驱动模型
块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,块设备(blockdevice)是一种具有一定结构的随机存取设备,对这种设备的读写是按块(所以叫块设备)进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。作为存储设备,块设备驱动的核心问题就是哪些page->segment->block->sector与哪些sector有数据交互,本文以3.14为蓝本,探讨内核中的块设备驱动模型。
框架
下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一个块设备:/dev和文件系统挂载点,前者和字符设备一样,通常用于配置,后者就是我们mount之后通过文件系统直接访问一个块设备了。
- read()系统调用最终会调用一个适当的VFS函数(read()-->sys_read()-->vfs_read()),将文件描述符fd和文件内的偏移量offset传递给它。
- VFS会判断这个SCI的处理方式,如果访问的内容已经被缓存在RAM中(磁盘高速缓存机制),就直接访问,否则从磁盘中读取
- 为了从物理磁盘中读取,内核依赖映射层mapping layer,即上图中的磁盘文件系统
- 确定该文件所在文件系统的块的大小,并根据文件块的大小计算所请求数据的长度。本质上,文件被拆成很多块,因此内核需要确定请求数据所在的块
- 映射层调用一个具体的文件系统的函数,这个层的函数会访问文件的磁盘节点,然后根据逻辑块号确定所请求数据在磁盘上的位置。
- 内核利用通用块层(generic block layer)启动IO操作来传达所请求的数据,通常,一个IO操作只针对磁盘上一组连续的块。
- IO调度程序根据预先定义的内核策略将待处理的IO进行重排和合并
- 块设备驱动程序向磁盘控制器硬件接口发送适当的指令,进行实际的数据操作
块设备 VS 字符设备
作为一种存储设备,和字符设备相比,块设备有以下几种不同:
字符设备 | 块设备 |
---|---|
1byte | 块,硬件块各有不同,但是内核都使用512byte描述 |
顺序访问 | 随机访问 |
没有缓存,实时操作 | 有缓存,不是实时操作 |
一般提供接口给应用层 | 块设备一般提供接口给文件系统 |
是被用户程序调用 | 由文件系统程序调用 |
此外,大多数情况下,磁盘控制器都是直接使用DMA方式进行数据传送。
IO调度
就是电梯算法。我们知道,磁盘是的读写是通过机械性的移动磁头来实现读写的,理论上磁盘设备满足块设备的随机读写的要求,但是出于节约磁盘,提高效率的考虑,我们希望当磁头处于某一个位置的时候,一起将最近需要写在附近的数据写入,而不是这写一下,那写一下然后再回来,IO调度就是将上层发下来的IO请求的顺序进行重新排序以及对多个请求进行合并,这样就可以实现上述的提高效率、节约磁盘的目的。这种解决问题的思路使用电梯算法,一个运行中的电梯,一个人20楼->1楼,另外一个人15->5楼,电梯不会先将第一个人送到1楼再去15楼接第二个人将其送到5楼,而是从20楼下来,到15楼的时候停下接人,到5楼将第二个放下,最后到达1楼,一句话,电梯算法最终服务的优先顺序并不按照按按钮的先后顺序。Linux内核中提供了下面的几种电梯算法来实现IO调度:
- No-op I/O scheduler只实现了简单的FIFO的,只进行最简单的合并,比较适合基于Flash的存储
- Anticipatory I/O scheduler推迟IO请求(大约几个微秒),以期能对他们进行排序,获得更高效率
- Deadline I/O scheduler试图把每次请求的延迟降到最低,同时也会对BIO重新排序,特别适用于读取较多的场合,比如数据库
- CFQ I/O scheduler为系统内所有的任务分配均匀的IO带宽,提供一个公平的工作环境,在多媒体环境中,能保证音视频及时从磁盘中读取数据,是当前内核默认的调度器
我们可以通过内核传参的方式指定使用的调度算法
kernel elevator=deadline
或者,使用如下命令改变内核调度算法
echo SCHEDULER > /sys/block/DEVICE/queue/scheduler
Page->Segment->Block->Sector VS Sector
VS左面的是数据交互中的内存部分,Page就是内存映射的最小单位; Segment就是一个Page中我们要操作的一部分,由若干个相邻的块组成; Block是逻辑上的进行数据存取的最小单位,是文件系统的抽象,逻辑块的大小是在格式化的时候确定的, 一个 Block 最多仅能容纳一个文件(即不存在多个文件同一个block的情况)。如果一个文件比block小,他也会占用一个block,因而block中空余的空间会浪费掉。而一个大文件,可以占多个甚至数十个成百上千万的block。Linux内核要求 Block_Size = Sector_Size * (2的n次方),并且Block_Size <= 内存的Page_Size(页大小), 如ext2 fs的block缺省是4k。若block太大,则存取小文件时,有空间浪费的问题;若block太小,则硬盘的 Block 数目会大增,而造成 inode 在指向 block 的时候的一些搜寻时间的增加,又会造成大文件读写方面的效率较差,block是VFS和文件系统传送数据的基本单位。block对应磁盘上的一个或多个相邻的扇区,而VFS将其看成是一个单一的数据单元,块设备的block的大小不是唯一的,创建一个磁盘文件系统时,管理员可以选择合适的扇区的大小,同一个磁盘的几个分区可以使用不同的块大小。此外,对块设备文件的每次读或写操作是一种"原始"访问,因为它绕过了磁盘文件系统,内核通过使用最大的块(4096)执行该操作。Linux对内存中的block会被进一步划分为Sector,Sector是硬件设备传送数据的基本单位,这个Sector就是512byte,和物理设备上的概念不一样,如果实际的设备的sector不是512byte,而是4096byte(eg SSD),那么只需要将多个内核sector对应一个设备sector即可
VS右边是物理上的概念,磁盘中一个Sector是512Byte,SSD中一个Sector是4K
核心结构与方法简述
核心结构
gendisk是一个物理磁盘或分区在内核中的描述
block_device_operations描述磁盘的操作方法集,block_device_operations之于gendisk,类似于file_operations之于cdev
request_queue对象表示针对一个gendisk对象的所有请求的队列,是相应gendisk对象的一个域
request表示经过IO调度之后的针对一个gendisk(磁盘)的一个"请求",是request_queue的一个节点。多个request构成了一个request_queue
bio表示应用程序对一个gendisk(磁盘)原始的访问请求,一个bio由多个bio_vec,多个bio经过IO调度和合并之后可以形成一个request。
bio_vec描述的应用层准备读写一个gendisk(磁盘)时需要使用的内存页page的一部分,即上文中的"段",多个bio_vec和bio_iter形成一个bio
bvec_iter描述一个bio_vec中的一个sector信息
核心方法
set_capacity()设置gendisk对应的磁盘的物理参数
blk_init_queue()分配+初始化+绑定一个有IO调度的gendisk的requst_queue,处理函数是void (request_fn_proc) (struct request_queue *q);类型
blk_alloc_queue() 分配+初始化一个没有IO调度的gendisk的request_queue,
blk_queue_make_request()绑定处理函数到一个没有IO调度的request_queue,处理函数函数是void (make_request_fn) (struct request_queue *q, struct bio *bio);类型
__rq_for_each_bio()遍历一个request中的所有的bio
bio_for_each_segment()遍历一个bio中所有的segment
rq_for_each_segment()遍历一个request中的所有的bio中的所有的segment
最后三个遍历算法都是用在request_queue绑定的处理函数中,这个函数负责对上层请求的处理。
核心结构与方法详述
gendisk
同样是面向对象的设计方法,Linux内核使用gendisk对象描述一个系统的中的块设备,类似于Windows系统中的磁盘分区和物理磁盘的关系,OS眼中的磁盘都是逻辑磁盘,也就是一个磁盘分区,一个物理磁盘可以对应多个磁盘分区,在Linux中,这个gendisk就是用来描述一个逻辑磁盘,也就是一个磁盘分区。
165 struct gendisk {
169 int major; /* major number of driver */
170 int first_minor;
171 int minors;
174 char disk_name[DISK_NAME_LEN]; /* name of major driver */
175 char *(*devnode)(struct gendisk *gd, umode_t *mode);
177 unsigned int events; /* supported events */
178 unsigned int async_events; /* async events, subset of all */
185 struct disk_part_tbl __rcu *part_tbl;
186 struct hd_struct part0;
188 const struct block_device_operations *fops;
189 struct request_queue *queue;
190 void *private_data;
192 int flags;
193 struct device *driverfs_dev; // FIXME: remove
194 struct kobject *slave_dir;
196 struct timer_rand_state *random;
197 atomic_t sync_io; /* RAID */
198 struct disk_events *ev;
200 struct blk_integrity *integrity;
202 int node_id;
203 };
struct gendisk
--169-->驱动的主设备号
--170-->第一个次设备号
--171-->次设备号的数量,即允许的最大分区的数量,1表示不允许分区
--174-->设备名称
--185-->分区表数组首地址
--186-->第一个分区,相当于part_tbl->part[0]
--188-->操作方法集指针
--189-->请求对象指针
--190-->私有数据指针
--193-->表示这是一个设备
gendisk是一个动态分配的结构体,所以不要自己手动来分配,而是使用内核相应的API来分配,其中会做一些初始化的工作
struct gendisk *alloc_disk(int minors);
//注册gendisk类型对象到内核
void add_disk(struct gendisk *disk);
//从内核注销gendisk对象
void del_gendisk(struct gendisk *gp);
上面几个API是一个块设备驱动中必不可少的部分,下面的两个主要是用来内核对于设备管理用的,通常不要驱动来实现
//对gendisk的引用计数+1
struct kobject *get_disk(struct gendisk *disk);
//对gendisk的引用计数-1
void put_disk(struct gendisk *disk);
这两个API最终回调用kobject *get_disk() 和kobject_put()来实现对设备的引用计数
block_device_operations
和字符设备一样,如果使用/dev接口访问块设备,最终就会回调这个操作方法集的注册函数
//include/linux/blkdev.h
1558 struct block_device_operations {
1559 int (*open) (struct block_device *, fmode_t);
1560 void (*release) (struct gendisk *, fmode_t);
1561 int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
1562 int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
1563 int (*direct_access) (struct block_device *, sector_t,
1564 void **, unsigned long *);
1565 unsigned int (*check_events) (struct gendisk *disk,
1566 unsigned int clearing);
1568 int (*media_changed) (struct gendisk *);
1569 void (*unlock_native_capacity) (struct gendisk *);
1570 int (*revalidate_disk) (struct gendisk *);
1571 int (*getgeo)(struct block_device *, struct hd_geometry *);
1573 void (*swap_slot_free_notify) (struct block_device *, unsigned long);
1574 struct module *owner;
1575 };
struct block_device_operations
--1559-->当应用层打开一个块设备的时候被回调
--1560-->当应用层关闭一个块设备的时候被回调
--1562-->相当于file_operations里的compat_ioctl,不过块设备的ioctl包含大量的标准操作,所以在这个接口实现的操作很少
--1567-->在移动块设备中测试介质是否改变的方法,已经过时,同样的功能被check_event()实现
--1571-->即get geometry,获取驱动器的几何信息,获取到的信息会被填充在一个hd_geometry结构中
--1574-->模块所属,通常填THIS_MODULE
request_queue
每一个gendisk对象都有一个request_queue对象,前文说过,块设备有两种访问接口,一种是/dev下,一种是通过文件系统,后者经过IO调度在这个gendisk->request_queue上增加请求,最终回调与request_queue绑定的处理函数,将这些请求向下变成具体的硬件操作
294 struct request_queue {
298 struct list_head queue_head;
300 struct elevator_queue *elevator;
472 };
struct request_queue
--298-->请求队列的链表头
--300-->请求队列使用的IO调度算法, 通过内核启动参数来选择: kernel elevator=deadline
request_queue_t和gendisk一样需要使用内核API来分配并初始化,里面大量的成员不要直接操作, 此外, 请求队列如果要正常工作还需要绑定到一个处理函数中, 当请求队列不为空时, 处理函数会被回调, 这就是块设备驱动中处理请求的核心部分!
从驱动模型的角度来说, 块设备主要分为两类需要IO调度的和不需要IO调度的, 前者包括磁盘, 光盘等, 后者包括Flash, SD卡等, 为了保证模型的统一性 , Linux中对这两种使用同样的模型但是通过不同的API来完成上述的初始化和绑定
有IO调度类设备API
//初始化+绑定
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
无IO调度类设备API
//初始化
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
//绑定
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
共用API
针对请求队列的操作是块设备的一个核心任务, 其实质就是对请求队列操作函数的编写, 这个函数的主要功能就是从请求队列中获取请求并根据请求进行相应的操作 内核中已经提供了大量的API供该函数使用
//清除请求队列, 通常在卸载函数中使用
void blk_cleanup_queue(struct request_queue *q)
//从队列中去除请求
blkdev_dequeue_request()
//提取请求
struct request *blk_fetch_request(struct request_queue *q)
//从队列中去除请求
struct request *blk_peek_request(struct request_queue *q)
//启停请求队列, 当设备进入到不能处理请求队列的状态时,应通知通用块层
void blk_stop_queue(struct request_queue *q)
void blk_start_queue(struct request_queue *q)
request
97 struct request {
98 struct list_head queuelist;
104 struct request_queue *q;
117 struct bio *bio;
118 struct bio *biotail;
119
120 struct hlist_node hash; /* merge hash */
126 union {
127 struct rb_node rb_node; /* sort/lookup */
128 void *completion_data;
129 };
137 union {
138 struct {
139 struct io_cq *icq;
140 void *priv[2];
141 } elv;
142
143 struct {
144 unsigned int seq;
145 struct list_head list;
146 rq_end_io_fn *saved_end_io;
147 } flush;
148 };
149
150 struct gendisk *rq_disk;
151 struct hd_struct *part;
199 };
struct request
--98-->将这个request挂接到链表的节点
--104-->这个request从属的request_queue
--117-->组成这个request的bio链表的头指针
--118-->组成这个request的bio链表的尾指针
--120-->内核hash表头指针
bio
bio用来描述单一的I/O请求,它记录了一次I/O操作所必需的相关信息,如用于I/O操作的数据缓存位置,,I/O操作的块设备起始扇区,是读操作还是写操作等等
46 struct bio {
47 struct bio *bi_next; /* request queue link */
48 struct block_device *bi_bdev;
49 unsigned long bi_flags; /* status, command, etc */
50 unsigned long bi_rw; /* bottom bits READ/WRITE,
51 * top bits priority
52 */
54 struct bvec_iter bi_iter;
59 unsigned int bi_phys_segments;
65 unsigned int bi_seg_front_size;
66 unsigned int bi_seg_back_size;
68 atomic_t bi_remaining;
70 bio_end_io_t *bi_end_io;
72 void *bi_private;
85 unsigned short bi_vcnt; /* how many bio_vec's */
91 unsigned short bi_max_vecs; /* max bvl_vecs we can hold */
104 struct bio_vec bi_inline_vecs[0];
105 };
struct bio
--47-->指向链表中下一个bio的指针bi_next
--50-->bi_rw低位表示读写READ/WRITE, 高位表示优先级
--90-->bio对象包含bio_vec对象的数目
--91-->这个bio能承载的最大的io_vec的数目
--95-->该bio描述的第一个io_vec
--104-->表示这个bio包含的bio_vec变量的数组,即这个bio对应的某一个page中的一"段"内存
bio_vec
描述指定page中的一块连续的区域,在bio中描述的就是一个page中的一个"段"(segment)
25 struct bio_vec {
26 struct page *bv_page;
27 unsigned int bv_len;
28 unsigned int bv_offset;
29 };
struct bio_vec
--26-->描述的page
--27-->描述的长度
--28-->描述的起始地址偏移量
bio_iter
用于记录当前bvec被处理的情况,用于遍历bio
31 struct bvec_iter {
32 sector_t bi_sector; /* device address in 512 byt
33 sectors */
34 unsigned int bi_size; /* residual I/O count */
35
36 unsigned int bi_idx; /* current index into bvl_ve
37
38 unsigned int bi_bvec_done; /* number of bytes completed
39 current bvec */
40 };
__rq_for_each_bio()
遍历一个request中的每一个bio
738 #define __rq_for_each_bio(_bio, rq) \
739 if ((rq->bio)) \
740 for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
bio_for_each_segment()
遍历一个bio中的每一个segment
242 #define bio_for_each_segment(bvl, bio, iter) \
243 __bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)
rq_for_each_segment()
遍历一个request中的每一个segment
742 #define rq_for_each_segment(bvl, _rq, _iter) \
743 __rq_for_each_bio(_iter.bio, _rq) \
744 bio_for_each_segment(bvl, _iter.bio, _iter.iter)
小结
遍历request_queue,绑定函数的一个必要的工作就是将request_queue中的数据取出, 所以遍历是必不可少的, 针对有IO调度的设备, 我们需要从中提取请求再继续操作, 对于没有IO调度的设备, 我们可以直接从request_queue中提取bio进行操作, 这两种处理函数的接口就不一样,下面的例子是对LDD3中的代码进行了修剪而来的,相应的API使用的是3.14版本,可以看出这两种模式的使用方法的不同。
sbull_init
└── setup_device
├──sbull_make_request
│ ├──sbull_xfer_bio
│ └──sbull_transfer
└──sbull_full_request
├──blk_fetch_request
└──sbull_xfer_request
├── __rq_for_each_bio
└── sbull_xfer_bio
└──sbull_transfer
/*
* Handle an I/O request.
* 实现扇区的读写
*/
static void sbull_transfer(struct sbull_dev *dev, unsigned long sector,unsigned long nsect, char *buffer, int write)
{
unsigned long offset = sector*KERNEL_SECTOR_SIZE;
unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;
if (write)
memcpy(dev->data + offset, buffer, nbytes);
else
memcpy(buffer, dev->data + offset, nbytes);
}
/*
* Transfer a single BIO.
*/
static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
{
struct bvec_iter i; //用来遍历bio_vec对象
struct bio_vec bvec;
sector_t sector = bio->bi_iter.bi_sector;
/* Do each segment independently. */
bio_for_each_segment(bvec, bio, i) { //bvec会遍历bio中每一个bio_vec对象
char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
sbull_transfer(dev, sector, bio_cur_bytes(bio)>>9 ,buffer, bio_data_dir(bio) == WRITE);
sector += bio_cur_bytes(bio)>>9;
__bio_kunmap_atomic(bio, KM_USER0);
}
return 0; /* Always "succeed" */
}
/*
* Transfer a full request.
*/
static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
{
struct bio *bio;
int nsect = 0;
__rq_for_each_bio(bio, req) {
sbull_xfer_bio(dev, bio);
nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
}
return nsect;
}
/*
* Smarter request function that "handles clustering".*/
static void sbull_full_request(struct request_queue *q)
{
struct request *req;
int nsect;
struct sbull_dev *dev ;
int i = 0;
while ((req = blk_fetch_request(q)) != NULL) {
dev = req->rq_disk->private_data;
nsect = sbull_xfer_request(dev, req);
__blk_end_request(req, 0, (nsect<<9));
printk ("i = %d\n", ++i);
}
}
//The direct make request version
static void sbull_make_request(struct request_queue *q, struct bio *bio)
{
struct sbull_dev *dev = q->queuedata;
int status;
status = sbull_xfer_bio(dev, bio);
bio_endio(bio, status);
return;
}
/*
* The device operations structure.
*/
static struct block_device_operations sbull_ops = {
.owner = THIS_MODULE,
.open = sbull_open,
.release= sbull_release,
.getgeo = sbull_getgeo,
};
/*
* Set up our internal device.
*/
static void setup_device(struct sbull_dev *dev, int which)
{
/*
* Get some memory.
*/
memset (dev, 0, sizeof (struct sbull_dev));
dev->size = nsectors * hardsect_size;
dev->data = vmalloc(dev->size);
/*
* The I/O queue, depending on whether we are using our own
* make_request function or not.
*/
switch (request_mode) {
case RM_NOQUEUE:
dev->queue = blk_alloc_queue(GFP_KERNEL);
blk_queue_make_request(dev->queue, sbull_make_request);
break;
case RM_FULL:
dev->queue = blk_init_queue(sbull_full_request, &dev->lock);
break;
}
dev->queue->queuedata = dev;
/*
* And the gendisk structure.
*/
dev->gd = alloc_disk(SBULL_MINORS);
dev->gd->major = sbull_major;
dev->gd->first_minor = which*SBULL_MINORS;
dev->gd->fops = &sbull_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a');
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd);
return;
}
static int __init sbull_init(void)
{
int i;
/*
* Get registered.
*/
sbull_major = register_blkdev(sbull_major, "sbull");
/*
* Allocate the device array, and initialize each one.
*/
Devices = (struct sbull_dev *)kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL);
for (i = 0; i < ndevices; i++)
setup_device(Devices + i, i);
return 0;
}
Linux块设备驱动(一) _驱动模型的更多相关文章
- Linux块设备IO子系统(一) _驱动模型
块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,块设备(blockdevice)是一种具有一定结构的随机存取设备,对这种设备的读写是按块(所以叫块设备)进行的,他使用缓 ...
- linux块设备驱动之实例
1.注册:向内核注册个块设备驱动,其实就是用主设备号告诉内核这个代表块设备驱动 sbull_major = register_blkdev(sbull_major, "sbull&quo ...
- Linux块设备驱动详解
<机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...
- linux块设备模型架构框架
Linux块设备的原理远比字符设备要复杂得多,尽管在linux这一块的方法论有很多相似之处,但考虑到它是用中块结构,它常常要搭配内存页管理,页缓冲块缓冲来改善硬盘访问的速度,按照块硬件最大的性能要求进 ...
- (linux)块设备驱动程序
1.4.1 Linux块设备驱动程序原理(1) 顾名思义,块设备驱动程序就是支持以块的方式进行读写的设备.块设备和字符设备最大的区别在于读写数据的基本单元不同.块设备读写数据的基本单元为块,例如 ...
- (linux)初学者学linux块设备驱动
面对不断升级的linux内核.GNU开发工具.linux环境下的各种图形库,很多linux应用程序开发人员和linux设备驱动开发人员即兴奋,又烦躁.兴奋的是新的软件软件.工具给我提供了更强大的功 ...
- linux块设备驱动---相关结构体(转)
上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...
- Linux 块设备驱动 (二)
linux下Ramdisk驱动 1 什么是Ramdisk Ramdisk是一种模拟磁盘,其数据实际上是存储在RAM中,它使用一部分内存空间来模拟出一个磁盘设备,并以块设备的方式来组织和访问这片内存.对 ...
- Linux 块设备驱动 (一)
1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...
随机推荐
- 从jvm的角度来看单例模式
最近在看jvm,发现随着自己对jvm底层的了解,现在对java代码可以说是有了全新的认识.今天就从jvm的角度来看一看以前自以为很了解的单例模式. 了解单例模式的人都知道,单例模式有两种:" ...
- 自述创业史 | XMove动作感应系统(二)
XMove是沙漠君和几个死党从2010年开始开发的一套人体动作捕捉系统,软硬件全部自行开发,投入了大量的精力,历经三年,发展四个版本.文章分上下篇,本文为下篇,前三代的故事在<光荣与梦想| XM ...
- asp.net 验证码技术
网站验证码是一种很常用的技术.下面我介绍下技术上是如何实现的. 验证码是一张图片.我们需要在前台代码中写一段<img>,src指向一张页面(ValidateImage.aspx). < ...
- MySQL管理命令
1.验证MySQL安装 在成功安装Mysql后,一些基础表会表初始化,在服务器启动后,你可以通过简单的测试来验证Mysql是否工作正常. 使用 mysqladmin 工具来获取服务器状态: 使用 my ...
- headfirst设计模式(2)—观察者模式
定义 观察者模式(有时又被称为发布(publish)-订阅(Subscribe)模式,在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知.这通常透过呼叫各观察 ...
- Swift2.0 函数学习笔记
最近又有点忙,忙着找工作,忙着适应这个新环境.现在好了,上班两周周了,也适应过来了,又有时间安安静静的就行我们前面的学习了.今天这篇笔记,记录的就是函数的使用.下面这些代码基本上是理清楚了函数的额使用 ...
- Jenkins在windows上的安装配置
今天是2月14号,所谓西方情人节,下班回来发现,2月14过的比七夕还火热.于是上网百度百科查询了"情人节". 毕竟是中国的百度啊.是这么解释的.我感到很欣慰.过得每一个节日都应该 ...
- wpf后置代码中的Grid布局以及图片路径的设置
之前用Grid练习连连看布局时,遇到了几个困惑.此次就把当时的一些收获写出来,供以后翻看. 图片路径可能比较常用,所以就写在第一个了. 在xaml中,设置图片非常简单,只要把图片拷贝到资源目录(这里假 ...
- Apache Pig处理数据示例
Apache Pig是一个高级过程语言,可以调用MapReduce查询大规模的半结构化数据集. 样例执行的环境为cloudera的单节点虚拟机 读取结构数据中的指定列 在hdfs上放置一个文件 [cl ...
- 在Angular中,如果权限值是异步请求所得,如何将其设置为HTTP请求头的Authorization?
遇到此问题的背景:项目需要实现单点登录,在前后端分离的前提下,前台如何保存token值成为了一个问题.想到的解决方案是,将token值统一存到一个前端程序,其他的前端程序去这个前端程序去取token( ...