25、写一个USB摄像头驱动程序(有ioctrl分析)
videobuf2-core.h中的vb2_buffer,记录了v4l2_buffer ,驱动可以对vb2_buffer的v4l2_buffer进行操控,
vb2_buffer是v4l2框架层的代码,而v4l2_buffer也是用户空间的应用程序,可以读取的一个数据结构。 在reqbuf的时候把每个vb2_buffer会放入vb2_queue中,通过vb2_buffer的index在数组中保存
uvc_init -> uvc_driver.c
usb_register -> //注册到usb core
uvc_probe -> //摄像头插入,枚举成功后被调用
uvc_parse_control -> //解析除了四大类描述符之外的所有描述符,四大类描述符在usb枚举的时候已经解析过了.
uvc_parse_vendor_control //解析vendor-specific控制描述符,这里貌似只有Logitech厂商的.
uvc_parse_standard_control -> //包括VS, 所有的entities,比如IT,OT,PU,SU等, Unit和Terminal会添加到uvc_device的entities链表.
uvc_parse_streaming -> //先解析VC HEADER,即Class-Specific VC Interface Descriptor
//, 里面会包含VC的总长度,当前版本,以及有几个VS, 然后循环调用uvc_parse_streaming解析VS.
//包括VS HEADER, frame, format等, 主要填充uvc_streaming结构体.最终被添加到uvc_device的streams链表.
uvc_parse_format //解析format描述符.
v4l2_device_register //注册成为标准的v4l2 device,对于Camera HAL来说,uvc和一般camera是一样的行为.
uvc_ctrl_init_device -> //根据前面得到的不同的entities相关信息都放到struct uvc_control中管理.
uvc_ctrl_init_ctrl //从硬件得到的CTRL和代码中原先的配置和mappming做匹配,然后添加到uvc_device中.
uvc_scan_device -> //针对不是OT的entities创建struct uvc_video_chain并绑定起来.
uvc_scan_chain //entity和video chain绑定.
list_add_tail //添加到uvc device中管理.
uvc_register_chains -> 根据前面得到的chain后进行注册
uvc_register_terms -> //只有type是UVC_TT_STREAMING的才能注册,也就是说一个stream对应注册一个video.
uvc_register_video ->
uvc_video_init
uvc_queue_init //video buffer queue初始化
uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);//设置queue的各个操作函数,会在reqsbuf时候用来分配buf
usb_set_interface
video_set_drvdata(vdev, stream);//非常重要:这里把stream和video_device真的关联起来,open的时候找到这个会根据设备号找到video_device,在找到stream,这个结构体来ioctrl中都会被用到
uvc_get_video_ctrl //把设备的配置作为stream control的默认参数.
video_device_alloc
video_register_device //注册的v4l2 device对应的ops是uvc_fops
uvc_status_init //USB input/urb相关内容初始化
uvc_input_init -> //camera是作为一个input设备存在的.
input_allocate_device
input_register_device
usb_alloc_urb
usb_rcvintpipe //创建接收中断endpoint.
usb_fill_int_urb //urb填充
1.构造一个usb_driver
2.设置
probe:
2.1. 分配video_device:video_device_alloc
2.2. 设置
.fops
.ioctl_ops (里面需要设置11项)
如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
2.3. 注册: video_register_device
id_table: 表示支持哪些USB设备
3.注册: usb_register
注册成功后,当usb设备插入时导致hub_irq被调用,会唤醒休眠的线程,线程中执行hub_event函数,会分配设置usb_device,组成设备的时候调用总线的match函数去匹配id_table,复合时调用driver的probe函数,uvc_driver驱动在probe函数中会顶层是uvc_device结构体,里面的steams链表保存VideoStreamingInterfaces接口,entities和chains保存VideoControlInterfaces接口,两者都在uvc_parse_control(解析除了四大类描述符之外的所有描述符,四大类描述符在usb枚举的时候已经解析过了.)函数中被挂接各个结构体,chains链表上挂接uvc_video_chain结构体,其与entities中每个entity相关联,steams链表挂接uvc_device里的streams链表上,在streams这个结构体里面有当前接口的format格式和frame以及struct uvc_video_queue queue结构体,其有个vb2_queue,里面有缓冲区地址和长度信息,还有各个函数用来分配buf(具体见上面代码分析)
(UVC规格,一般即插即用的USB摄像头都复合该规则)
UVC: USB Video Class
UVC驱动:drivers\media\video\uvc\
uvc_driver.c分析:
1. usb_register(&uvc_driver.driver);
2. uvc_probe
uvc_register_video
uvc_video_init
uvc_queue_init//初始化队列,提供操作队列的函数
vdev = video_device_alloc();
vdev->fops = &uvc_fops;
video_register_device
在www.usb.org下载 uvc specification,
UVC 1.5 Class specification.pdf : 有详细描述
USB_Video_Example 1.5.pdf : 有示例
通过VideoControl Interface来控制,一般只有一个
通过VideoStreaming Interface来读视频数据,一般有一个或者多个
VC里含有多个Unit/Terminal等功能模块,可以通过访问这些模块进行控制,比如调亮度
SU:select Unit PU:process Unnit,还有一些其他Unit,可以从UVC 1.5 Class specification.pdf中看到
IT:input terminal CT:Camera Terminal OT:out terminal
Terminal用于‘内‘’外’链接,Unit是里面的功能模块
Terminal和Unit在代码中被统称为entity
PU用于控制图像的属性
分析UVC驱动调用过程:
const struct v4l2_file_operations uvc_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.ioctl = uvc_v4l2_ioctl,
.read = uvc_v4l2_read,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
};
1. open:
uvc_v4l2_open
stream = video_drvdata(file);
video_get_drvdata(video_devdata(file));//video_devdata(file)就是找到的video_device
dev->p->driver_data //返回的就是streams,p是device_private指针
(uvc_v4l2_ioctl->video_usercopy->uvc_v4l2_do_ioctl(这个函数在video_usercopy的参数中被使用))
2. VIDIOC_QUERYCAP // video->streaming->type 应该是在设备被枚举时分析描述符时设置的
if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
else
cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
3. VIDIOC_ENUM_FMT // format数组应是在设备被枚举时设置的
format = &video->streaming->format[fmt->index];(video->streaming可以理解为上图中的VideoStreaming Interface)
4. VIDIOC_G_FMT
uvc_v4l2_get_format // USB摄像头支持多种格式fromat, 每种格式下有多种frame(比如分辨率)
struct uvc_format *format = video->streaming->cur_format;(cur_format应是在设备被枚举时设置的)
struct uvc_frame *frame = video->streaming->cur_frame;
5. VIDIOC_TRY_FMT
uvc_v4l2_try_format
/* Check if the hardware supports the requested format. */找到是否支持format
/* Find the closest image size. The distance between image sizes is//找到接近的frame
* the size in pixels of the non-overlapping regions between the
* requested size and the frame-specified size.
*/
6. VIDIOC_S_FMT // 只是把参数保存起来,还没有发给USB摄像头
uvc_v4l2_set_format
uvc_v4l2_try_format
streaming->cur_format = format;
streaming->cur_frame = frame;
7. VIDIOC_REQBUFS //仅分配了vb2_buffer的大小,存数据的buf没有分配(应该是分配了,这里写没有分配应该是受到分析vivi时的影响,可以参考第七课我们自己写的uvc驱动程序)
//同时,这里会调用queue的alloc函数给所有的planes分配空间,,后面mmap会通过__find_plane_by_offset函数负责根据用户空间提供的offset来找到对应的struct vb2_buffer *vb,对于V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE类型的视频捕获设备num_planes可以理解为缓冲buffer由多少个plane组成,其vb->v4l2_buf.length = num_planes;,视频数据应该真正存放在planes中,由vb2_buffer中的planes数组来保存,并通过__vb2_buf_mem_alloc给其分配空间,每个planes的大小保存在vb2_queue中,并且会保存在v4l2_planes中(对于非Capture_Mplane设备,缓冲buffer的length和offset都保存在vb2_buffer中的v4l2_planes[0]中,否则每个v4l2_buffer的m枚举变量是v4l2_plane指针,在querybuf的时候直接把v4l2_planes复制给m中的v4l2_plane指针planes)
(非plane视频捕获设备中planes的个数能理解为1个?)
ret = call_qop(q, queue_setup, q, &create->format, &num_buffers,&num_planes, q->plane_sizes, q->alloc_ctx);在该函数中调用队列的queue_setup来设置num_buffers、num_planesplane_sizes
if (*nbuffers > UVC_MAX_VIDEO_BUFFERS)
*nbuffers = UVC_MAX_VIDEO_BUFFERS;
*nplanes = 1;
sizes[0] = stream->ctrl.dwMaxVideoFrameSize;
//及每个buf大小为一帧数据组成,每个buf由一个plane组成
ret = __vb2_queue_alloc(q, create->memory, num_buffers,num_planes);
for (buffer = 0; buffer < num_buffers; ++buffer)
vb->vb2_queue = q;
vb->num_planes = num_planes;
vb->v4l2_buf.index = q->num_buffers + buffer;
vb->v4l2_buf.type = q->type;
vb->v4l2_buf.memory = memory;
ret = __vb2_buf_mem_alloc(vb);
for (plane = 0; plane < vb->num_planes; ++plane) {
mem_priv = call_memop(q, alloc, q->alloc_ctx[plane],q->plane_sizes[plane]);
/* Associate allocator private data with this plane */
vb->planes[plane].mem_priv = mem_priv;
vb->v4l2_planes[plane].length = q->plane_sizes[plane];
}
q->bufs[q->num_buffers + buffer] = vb;//vb2_queue(q)的vb2_buffer(bufs)里面保存视频数据,地址在vb2_buffer的planes.mem_priv
vb2_queue结构体的plane_sizes[0]保存有buf的大小
for (; nbuffers > 0; --nbuffers) {
mem = vmalloc_32(nbuffers * bufsize);
if (mem != NULL)
break;
}
8. VIDIOC_QUERYBUF
uvc_query_buffer
__uvc_query_buffer
memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf); // 复制参数
9. mmap
uvc_v4l2_mmap(3.4.2的内核在vb2的planes[plane].mem_priv中指定buf大小,mmap的时候已经将内核数据地址映射到用户空间,所以数据不用拷贝会去,但是如果视频数据是压缩格式,其每一帧的长度不一样,在dqbuf的时候需要把buf拷贝回去,里面有长度等信息)
(有效数据存在哪里?
解码前的和解码后的,都在vb->planes[plane].mem_priv里面,这个mem_priv是struct vb2_dc_buf ,里面记录了这块DMA内存的虚拟地址和物理地址,都是在vidioc_reqbufs----vb2_reqbufs----__vb2_queue_alloc(挂到q->bufs)---__vb2_buf_mem_alloc---- call_memop(q, alloc, q->alloc_ctx[plane]---------vb2_dma_contig_alloc的时候记录好的,通过buf->vaddr = dma_alloc_coherent)
//__find_plane_by_offset函数负责根据用户空间提供的offset来找到对应的struct vb2_buffer *vb,然后直接调用remap_vmalloc_range函数,将vmalloc空间的内存直接映射到用户空间中去,用户空间的地址范围在(vma->vm_start,vma->vm_end)。
10. VIDIOC_QBUF
uvc_queue_buffer
list_add_tail(&buf->stream, &queue->mainqueue);
list_add_tail(&buf->queue, &queue->irqqueue);
3.4.2的内核中
uvc_queue_buffer
vb2_qbuf
vb = q->bufs[b->index];//根据index取出vb2_buffer
__buf_prepare
uvc_buffer_prepare
list_add_tail(&vb->queued_entry, &q->queued_list);
另外一个done_list在urb的传输完成函数uvc_video_complete中调用,及有数据的时候才将buf放入done_list队列
uvc_video_complete //这个函数是在streamOn的时候注册的回调函数
stream->decode(urb, stream, buf);//就是uvc_video_decode_isoc(实时端点)/uvc_video_encode_bulk
uvc_video_decode_isoc
uvc_video_decode_data//把urb里面的数据复制到uvc_buffer结构体中的vb2_buffer中的v4l2_buffer中
uvc_queue_next_buffer//把vb2_buffer根据其内done_entry链表当然到vb2_queue的done_list中
vb2_buffer_done
list_add_tail(&vb->done_entry, &q->done_list);
wake_up(&q->done_wq);
11. VIDIOC_STREAMON//驱动中,会将之前通过协商设置的参数(图像格式(yuv,mjpeg,h264等),宽,高,帧率等)通过commit命令(VS_COMMIT_CONTROL )发送到设备,让其生效,并开始工作。在分配和初始bulk/iso 的urb后(包括设置urb的完成回调函数,回调函数在收到完整urb包的时候会被调用,对于实时端点,一个urb包包含多个iso包),最后调用usb_submit_urb函数,将这个urb提交到usb hcd驱动核心的urb传输队列中。这样urb就可以开始收usb控制器接收到的视频数据料。
/* 1. 向USB摄像头设置参数: 比如使用哪个format, 使用这个format下的哪个frame(分辨率)
* 参考: uvc_set_video_ctrl / uvc_get_video_ctrl
* 1.1 根据一个结构体uvc_streaming_control设置数据包: 可以手工uvc_set_video_ctrl 设置,也可以uvc_get_video_ctrl读出后再修改
* 1.2 调用usb_control_msg发出数据包
说明:,usb 2.0规范里面指出,所有的usb设备都会响应主机的一些请求,这些请求必须来自主机,通过设备的默认控制管道发出.(即0号端点所对应的那个管道)这些请求,或者说这些request,是通过控制传输来实现的,请求以及请求所需的参数都是通过setup packet来发送的.主机负责建立好这个setup packet.(也就是咱们刚才的那个usb_ctrlrequest,后来的setup_packet.)每个setup packet包含8个bytes
usb_ctrlrequest分析:
byte0: bmRequestType
D7: 控制传输数据传输的方向,0表示方向为主机到设备,1表示方向为设备到主机.(有时控制传输除了发命令阶段以外,可能还有数据传输阶段,此处表示的是在数据传输那个阶段的传输方向.)
D6...5: 请求的类型,0称为标准类型(Standard),1称为Class,2称为Vendor,3则是保留的,不能使用.
D4...0: 接收者,0表示设备,1表示接口,2表示端点,3表示其它,4...31都是保留的,不能使用
byte1: bRequest,1个byte,指定了是哪个请求.每一个请求都有一个编号,咱们这里是GET MAX LUN,其编号是FEh.
byte2...3: wValue,2个bytes,不同请求有不同的值,咱们这里刚才已经说了,必须为0.
byte4...5: wIndex,2个bytes,不同请求有不同的值,咱们这里要求被设置为是interface number.
byte6...7: wLength,2个bytes,如果接下来有数据传输阶段,则这个值表征了数据传输阶段传输多少个bytes
控制信号的数据data其实就是uvc_streaming_control结构体
/
uvc_video_enable(video, 1) // 把所设置的参数发给硬件,然后启动摄像头,通过dev的端点0与uvc设备通讯
/* Commit the streaming parameters. */
uvc_commit_video
uvc_set_video_ctrl /* 设置格式fromat, frame,这些帧格式都是通过与VS通讯设置的,控制信号使用usb_fill_control_urb来填充,其中cmd是usb_ctrlrequest结构体其,只有图像的亮度等属性是与VC通讯设置,先try,仅设置部分参数,发给USB设备后,其如果支持就会补齐剩下的参数(但在组织urb的时候那个cmd还是要完整),接着get,最后set */
ret = __uvc_query_ctrl(video->dev /* 哪一个USB设备 */, SET_CUR, 0,
video->streaming->intfnum /* 哪一个接口: VideoStreaming ,intfnum 的值在probe中被赋值intf->cur_altsetting->desc.bInterfaceNumber,在我们写的驱动中,是识别streaming的时候赋值*/,
probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,//VS_PROBE_CONTROL 表示尝试,VS_COMMIT_CONTROL表示设置
uvc_timeout_param);
/* 启动:Initialize isochronous/bulk URBs and allocate transfer buffers. */
uvc_init_video(video, GFP_KERNEL);///* d. 设置VideoStreaming Interface所使用的setting(setting值的是接口的设置,一个接口有多个设置)
* d.1 从myuvc_params确定带宽,myuvc_params是在uvc_commit_video设置参数的时候传入的结构体里面dwMaxPayloadTransferSize一般在set之前会try和get,可以看我们自己写的驱动
* d.2 根据setting的endpoint能传输的wMaxPacketSize
* 找到能满足该带宽的setting
代码是if (intf->num_altsetting > 1){}
e、分配设置URB(分配了多个urb_buffer和urb用于接受数据,urb_buffer是一帧的大小,包含多个数据包,即一帧数据是分几次传输的)
f、提交URB以接收数据
uvc_init_video_isoc / uvc_init_video_bulk(会初始化接受数据urb,通讯端点根据描述符获取)
//注意3.4.2的内核complete与下面分析的略有不同,见上面qbuf中的分析
urb->complete = uvc_video_complete; (收到数据后此函数被调用,它又执行下面代码唤醒在poll中休眠的进程
buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,queue);//从irqqueue中取出队列,数据在mainqueue中在取出,数据会保存在buf中,在我们自己写的例子中buf的地址等于buf->buf.m.offset + buf->buf.bytesused(这个buf已经被用了多少byte,一般是0)+数据buf的首地址,也就是说数据buf在reqbuf时也分配了,那么之前的博客中说数据buf在在mmap的时候才分配,在reqbuf的时候只分配了vb2_buf是有问题的,应该是vivi虚拟摄像头中是这样做的,但我们的真实UVC是在reqbuf时分配了数据buf的地址,3.4.2的内核数据不在queue队列中,而是在done_list中
//3.4.2的内核是stream->decode,其是在uvc_driver.c中的probe函数中的uvc_register_video下的uvc_video_init中设置的
video->decode(urb, video, buf); ==>uvc_video_decode_isoc(实时端点)/uvc_video_encode_bulk(批量端点) =>uvc_queue_next_buffer => wake_up(&buf->wait);)
uvc_video_decode_isoc
mem = urb->transfer_buffer + urb->iso_frame_desc[i].offset;//mem是源
//下面两行的代码是从第七课自己写uvc驱动中复制过来的,应该在uvc_video_decode_data代码中处理目的和长度
dest = myuvc_queue.mem + buf->buf.m.offset + buf->buf.bytesused;
len = urb->iso_frame_desc[i].actual_length;
uvc_video_decode_start//对源数据格式进行判断
uvc_video_decode_data//从urb中复制数据到缓存buf中去,即从mem到dest,len应该减去head头部
//decode在uvc_video_init中被设置
usb_submit_urb
12. poll
uvc_v4l2_poll
uvc_queue_poll
poll_wait(file, &buf->wait, wait); // 休眠等待有数据
3.4.2的内核poll_wait(file, &q->done_wq, wait);// 休眠等待有数据,在uvc_video_complete 的时候wake_up(&q->done_wq)
13. VIDIOC_DQBUF
uvc_dequeue_buffer
list_del(&buf->stream);
3.4.2的内核
__vb2_get_done_vb
*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);//vb是vb2_buffer结构体,存放有数据
list_del(&(*vb)->done_entry);
list_del(&vb->queued_entry);
14. VIDIOC_STREAMOFF
uvc_video_enable(video, 0);
usb_kill_urb(urb);
usb_free_urb(urb);
分析设置亮度过程:
ioctl: VIDIOC_S_CTRL
uvc_ctrl_set(设置)
uvc_ctrl_commit(提交)
__uvc_ctrl_commit(video, 0);
uvc_ctrl_commit_entity(video->dev, entity, rollback);
ret = uvc_query_ctrl(dev /* 哪一个USB设备 */, SET_CUR, ctrl->entity->id /* 哪一个unit/terminal */,
dev->intfnum /* 哪一个接口: VideoControl interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);
总结:
1. UVC设备有2个interface: VideoControl Interface, VideoStreaming Interface
2. VideoControl Interface用于控制,比如设置亮度。它内部有多个Unit/Terminal(在程序里Unit/Terminal都称为entity)
可以通过类似的函数来访问:
ret = uvc_query_ctrl(dev /* 哪一个USB设备 */, SET_CUR, ctrl->entity->id /* 哪一个unit/terminal */,
dev->intfnum /* 哪一个接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);
3. VideoStreaming Interface用于获得视频数据,也可以用来选择fromat/frame(VS可能有多种format, 一个format支持多种frame, frame用来表示分辨率等信息)
可以通过类似的函数来访问:
ret = __uvc_query_ctrl(video->dev /* 哪一个USB设备 */, SET_CUR, 0,
video->streaming->intfnum /* 哪一个接口: VS */,
probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
uvc_timeout_param);
4. 我们在设置FORMAT时只是简单的使用video->streaming->format[fmt->index]等数据,
这些数据哪来的?
应是设备被枚举时设置的,也就是分析它的描述符时设置的。
5. UVC驱动的重点在于:
描述符的分析
属性的控制: 通过VideoControl Interface来设置
格式的选择:通过VideoStreaming Interface来设置
数据的获得:通过VideoStreaming Interface的URB来获得
25、写一个USB摄像头驱动程序(有ioctrl分析)的更多相关文章
- 2.5 USB摄像头驱动程序框架
学习目标:根据vivi驱动架构和linux-2.6.31/linux-2.6.31.14/drivers/media/video/uvc/Uvc_driver.c驱动源码,分析usb摄像头驱动程序框架 ...
- Linux usb子系统(一) _写一个usb鼠标驱动
USB总线是一种典型的热插拔的总线标准,由于其优异的性能几乎成为了当下大小设备中的标配. USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动 ...
- 十行代码--用python写一个USB病毒 (知乎 DeepWeaver)
昨天在上厕所的时候突发奇想,当你把usb插进去的时候,能不能自动执行usb上的程序.查了一下,发现只有windows上可以,具体的大家也可以搜索(搜索关键词usb autorun)到.但是,如果我想, ...
- 在Linux下写一个简单的驱动程序
本文首先描述了一个可以实际测试运行的驱动实例,然后由此去讨论Linux下驱动模板的要素,以及Linux上应用程序到驱动的执行过程.相信这样由浅入深.由具体实例到抽象理论的描述更容易初学者入手Linux ...
- 【转】在Linux下写一个简单的驱动程序
转自:https://www.cnblogs.com/amanlikethis/p/4914510.html 本文首先描述了一个可以实际测试运行的驱动实例,然后由此去讨论Linux下驱动模板的要素,以 ...
- C++写一个简单的解析器(分析C语言)
该方案实现了一个分析C语言的词法分析+解析. 注意: 1.简单语法,部分秕.它可以在本文法的基础上进行扩展,此过程使用自上而下LL(1)语法. 2.自己主动能达到求First 集和 Follow 集. ...
- 如何构造一个简单的USB过滤驱动程序
本文分三部分来介绍如何构造一个简单的USB过滤驱动程序,包括"基本原理"."程序的实现"."使用INF安装".此文的目的在于希望读者了解基本 ...
- Linux USB摄像头驱动【转】
本文转载自:http://www.itdadao.com/articles/c15a509940p0.html 在 cortex-a8 中,可接入摄像头的接口通常可以分为两种, CAMERA 接口和 ...
- Linux驱动之USB总线驱动程序框架简析
通用串行总线(USB)是主机和外围设备之间的一种连接.USB总线规范有1.1版和2.0版,当然现在已经有了3.0版本.USB1.1支持两种传输速度:低速为1.5Mbps,高速为12Mbps.USB2. ...
随机推荐
- 【Django】序列化
Django中序列化主要应用于将数据库中检索的数据返回给客户端用户,特别是Ajax请求一般返回为Json格式. * 1.from django.core import serializers** fr ...
- tee---读取标准输入,将内容输出成文件
- chattr---文件隐藏属性
- js获取单选button的值
<!DOCTYPE html> <html> <body> <script type="text/javascript"> func ...
- virmon防火墙64位正式版(暂定)公布
ChangeLog: 2015-06-2564位版本号签名问题临时得到解决.还要致谢一下某位黑客. 支持版本号x64 Windows Vista.7.8.8.1以上等.个人仅仅在Windows7上做了 ...
- canvas:动态时钟
此时针是以画布的中心为圆心: ctx.translate(width/2,width/2); 此函数是将画布的原点移到(width/2,width/2) 数字的位置我们利用了三角函数的原理 x=rco ...
- Python之字符串切片
切片操作(slice)可以从一个字符串中获取子字符串(字符串的一部分).我们使用一对方括号.起始偏移量start.终止偏移量end 以及可选的步长step 来定义一个分片. 格式: [start:en ...
- 解决使用SecureCRT不能连接Ubuntu的问题
一.现象 SecureCRT是远程登陆工具及串口,可以远程进行登陆Linux服务器或者串口打印数据.但我下载安装了之后想通过SecureCRT来远程登陆我的Ubuntu,出现一直连接不上. 二.问题原 ...
- 【2017 Multi-University Training Contest - Team 9】Numbers
[链接]http://acm.hdu.edu.cn/showproblem.php?pid=6168 [题意] 有一个长度为n的序列a1--an,根据a序列生成了一个b序列,b[i] = a[i]+a ...
- [D3JS] Add more map layer and color
import React, {Component} from 'react'; import * as d3 from 'd3'; import 'd3-geo'; import * as topoj ...