linux ioctl命令,关于LINUX下的ioctl函数
驱动程序中ioctl函数的函数原型如下:
int (*ioctl)(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg);
其中cmd和arg参数是ioctl与其它驱动程序函数不同的地方。cmd是预先定义好的一些命令编号,对应要求ioctl执行的命令。arg是与cmd配合使用的参数。
ioctl函数的功能比较繁琐,从函数名可以看出,它一般是实现对设备的各种控制操作。可以这样理解,通过常规的read,write,lseek等等函数实现不合理的功能,
就交给ioctl来实现。例如:要求设备锁门,弹出介质,改变波特率,甚至执行自我破坏,等等。
ioctl的实现一般是通过一个大的switch语句,根据cmd参数执行不同的操作。所以,在实现ioctl函数之前,要先定义好cmd对应的命令编号。
为了防止发生混淆,命令编号应该在系统范围内是唯一的。为此,Linux内核将命令编号分为4个部分,即4个位段,分别是:
type: 幻数(magic number),它占8位。个人理解幻数就是一个标志,代表一个(类)对象。后面我们会看到,scull使用字符’k’作为幻数。
number:序数,即顺序编号,它也占8位。
direction:如果相关命令涉及到数据的传输,则这个位段表示数据传输的方向,可用的值包括_IOC_NONE(没有数据传输),_IOC_READ(读)、_IOC_WRITE(写)、
_IOC_READ | _IOC_WRITE(双向传输数据)。注意,数据传输方向是从应用程序的角度看的,也就是说_IOC_READ意味着从设备中读数据,所以驱动程序
必须向用户空间写数据。
size:所涉及的用户数据大小。这个位段的宽度与体系结构有关,通常是13或14位。
中包含的头文件定义了一些构造命令编号的宏:
_IO(type, nr),用于构造无数据传输的命令编号。
_IOR(type, nr, datatype),用于构造从驱动程序中读取数据的命令编号。
_IOW(type, nr, datatype),用于构造向设备写入数据的命令编号。
_IOWR(type, nr, datatype),用于双向传输命令编号。
其中,type和number位段从以上宏的参数中传入,size位段通过对datatype参数取sizeof获得。
另外,头文件中还定义了一些用于解析命令编号的宏,如_IOC_DIR(cmd),_IOC_TYPE(cmd),_IOC_NR(cmd),_IOC_SIZE(cmd)。
首先我们来看一下scull是如何定义命令编号的,理解scull的ioctl函数的实现,关键是理解这些命令是什么含义,即要求完成什么工作。在scull.h中有如下定义:
/*
* Ioctl definitions
*/
/* Use 'k' as magic number 定义scull的幻数是字符'k'*/
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr, ’S’代表通过参数arg指向的内容设置
* T means "Tell" directly with the argument value, ’T’代表直接通过参数arg的值设置
* G means "Get": reply by setting through a pointer, ’G’代表通过参数arg指向的地址返回请求的值
* Q means "Query": response is on the return value , ’Q’代表通过ioctl函数的返回值返回请求的值。
* X means "eXchange": switch G and S atomically ’X’代表通过参数arg指向的内容设置,再把原来的值通过arg指向的地址返回。即’S’与’G’两个操作合为一步。
* H means "sHift": switch T and Q atomically ’H’代表通过参数arg的值直接设置,再通过ioctl函数的返回值将原来的值返回。即’T’和’Q’两个操作合为一步。
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) 该命令表示通过参数arg指向的内容设置quantum。
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) 该命令表示通过参数arg指向的内容设置qset。
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) 该命令表示通过参数arg的值直接设置quantum
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) 该命令表示通过参数arg的值直接设置qset。
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) 该命令表示通过参数arg指向的地址返回quantum
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) 该命令表示通过参数arg指向的地址返回qset。
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) 该命令表示通过ioctl的返回值返回quantum
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) 该命令表示通过ioctl的返回值返回qset。
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int) 该命令表示通过参数arg指向的内容设置quantum,然后,再把quantum原来的值写入arg指向的地址返回。
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int) 该命令表示通过参数arg指向的内容设置qset,然后,再把qset原来的值写入arg指向的地址返回。
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) 该命令表示通过参数arg的值直接设置quantum,然后,再通过ioctl的返回值返回quantum原来的值。
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) 该命令表示通过参数arg的值直接设置qset,然后,再通过ioctl的返回值返回qset原来的值。
/*
* The other entities only have "Tell" and "Query", because they're
* not printed in the book, and there's no need to have all six.
* (The previous stuff was only there to show different ways to do it.
*/
#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13) 该命令表示通过参数arg的值直接设置scull_p_buffer
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14) 该命令表示通过ioctl的返回值返回scull_p_buffer
/* ... more to come */
#define SCULL_IOC_MAXNR 14 代表一共有14个命令
理解了ioctl命令的含义后,来看scull.c中的ioctl函数:
/*
* The ioctl() implementation
*/
int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; /*如果不是幻数K,就退出*/
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; /*cmd的序数大于14,则退出*/
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*如果要使用arg指向的地址进行数据的读或写,必须保证对该地址的访问是合法的,这可通过access_ok函数来验证,如果访问不合法,则退出
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
switch(cmd) {
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN)) //scull允许任何用户查询quantum和qset的大小,
//但只允许被授权的用户修改quantum和qset的值。这种权能的检查是通过capable()函数实现的
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg); /*驱动程序与用户空间传递数据,
*采用的是__put_user和__get_user函数,相比copy_to_user和copy_from_user来说,
*这些函数在处理1、2、4、8个字节的数据传输时,效率更高*/
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval == 0)
retval = __put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return tmp;
case SCULL_IOCSQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCTQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_qset = arg;
break;
case SCULL_IOCGQSET:
retval = __put_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCQQSET:
return scull_qset;
case SCULL_IOCXQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
retval = __get_user(scull_qset, (int __user *)arg);
if (retval == 0)
retval = put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
scull_qset = arg;
return tmp;
/*
* The following two change the buffer size for scullpipe.
* The scullpipe device uses this same ioctl method, just to
* write less code. Actually, it's the same driver, isn't it?
*/
case SCULL_P_IOCTSIZE:
scull_p_buffer = arg;
break;
case SCULL_P_IOCQSIZE:
return scull_p_buffer;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
}
/***************************************/
二、测试ioctl
要测试scull驱动中ioctl函数是否实现了我们要求的功能,需要编写用户空间程序对scull模块进行测试。下面是一个比较简单的测试程序:
首先是头文件scull_ioctl.h:
#ifndef _SCULL_IOCTL_H_
#define _SCULL_IOCTL_H_
#include /* needed for the _IOW etc stuff used later */
/*
* Ioctl definitions
*/
/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
/*
* The other entities only have "Tell" and "Query", because they're
* not printed in the book, and there's no need to have all six.
* (The previous stuff was only there to show different ways to do it.
*/
#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14)
/* ... more to come */
#define SCULL_IOC_MAXNR 14
#endif /* _SCULL_IOCTL_H_ */
下面是测试程序scull_ioctl_test.c的代码:
#include
#include
#include
#include
#include
#include "scull_ioctl.h"
#define SCULL_DEVICE "/dev/scull0"
int main(int argc, char *argv[])
{
int fd = 0;
int quantum = 8000;
int quantum_old = 0;
int qset = 2000;
int qset_old = 0;
fd = open(SCULL_DEVICE, O_RDWR);
if(fd < 0)
{
printf("open scull device error!\n");
return 0;
}
printf("SCULL_IOCSQUANTUM: quantum = %d\n", quantum);
ioctl(fd, SCULL_IOCSQUANTUM, &quantum);
quantum -= 500;
printf("SCULL_IOCTQUANTUM: quantum = %d\n", quantum);
ioctl(fd, SCULL_IOCTQUANTUM, quantum);
ioctl(fd, SCULL_IOCGQUANTUM, &quantum);
printf("SCULL_IOCGQUANTUM: quantum = %d\n", quantum);
quantum = ioctl(fd, SCULL_IOCQQUANTUM);
printf("SCULL_IOCQQUANTUM: quantum = %d\n", quantum);
quantum -= 500;
quantum_old = ioctl(fd, SCULL_IOCHQUANTUM, quantum);
printf("SCULL_IOCHQUANTUM: quantum = %d, quantum_old = %d\n", quantum, quantum_old);
quantum -= 500;
printf("SCULL_IOCXQUANTUM: quantum = %d\n", quantum);
ioctl(fd, SCULL_IOCXQUANTUM, &quantum);
printf("SCULL_IOCXQUANTUM: old quantum = %d\n", quantum);
printf("SCULL_IOCSQSET: qset = %d\n", qset);
ioctl(fd, SCULL_IOCSQSET, &qset);
qset += 500;
printf("SCULL_IOCTQSET: qset = %d\n", qset);
ioctl(fd, SCULL_IOCTQSET, qset);
ioctl(fd, SCULL_IOCGQSET, &qset);
printf("SCULL_IOCGQSET: qset = %d\n", qset);
qset = ioctl(fd, SCULL_IOCQQSET);
printf("SCULL_IOCQQSET: qset = %d\n", qset);
qset += 500;
qset_old = ioctl(fd, SCULL_IOCHQSET, qset);
printf("SCULL_IOCHQSET: qset = %d, qset_old = %d\n", qset, qset_old);
qset += 500;
printf("SCULL_IOCXQSET: qset = %d\n", qset);
ioctl(fd, SCULL_IOCXQSET, &qset);
printf("SCULL_IOCHQSET: old qset = %d\n", qset);
return 0;
}
为了能看到测试效果,在修改驱动程序中的ioctl函数,打印一些语句。下面直接列出修改后的ioctl函数的实现:
/*
* The ioctl() implementation
*/
int scull_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
switch(cmd) {
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
printk("SCULL_IOCRESET: scull_quantum = %d, scull_qset = %d\n", scull_quantum, scull_qset);
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);
printk("SCULL_IOCSQUANTUM: scull_quantum = %d\n", scull_quantum);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
printk("SCULL_IOCTQUANTUM: scull_quantum = %d\n", scull_quantum);
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
printk("SCULL_IOCGQUANTUM: use arg return scull_quantum = %d\n", scull_quantum);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
printk("SCULL_IOCQQUANTUM: return scull_quantum = %d\n", scull_quantum);
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval == 0)
retval = __put_user(tmp, (int __user *)arg);
printk("SCULL_IOCXQUANTUM: scull_quantum = %d, and use arg return old scull_quantum = %d\n", scull_quantum, tmp);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
printk("SCULL_IOCHQUANTUM: scull_quantum = %d, and return old scull_quantum = %d\n", scull_quantum, tmp);
return tmp;
case SCULL_IOCSQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_qset, (int __user *)arg);
printk("SCULL_IOCSQSET: scull_qset = %d\n", scull_qset);
break;
case SCULL_IOCTQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_qset = arg;
printk("SCULL_IOCTQSET: scull_qset = %d\n", scull_qset);
break;
case SCULL_IOCGQSET:
retval = __put_user(scull_qset, (int __user *)arg);
printk("SCULL_IOCGQSET: use arg return scull_qset = %d\n", scull_qset);
break;
case SCULL_IOCQQSET:
printk("SCULL_IOCQQSET: return scull_qset = %d\n", scull_qset);
return scull_qset;
case SCULL_IOCXQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
retval = __get_user(scull_qset, (int __user *)arg);
if (retval == 0)
retval = put_user(tmp, (int __user *)arg);
printk("SCULL_IOCXQSET: scull_qset = %d, and use arg return old scull_qset = %d\n", scull_qset, tmp);
break;
case SCULL_IOCHQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
scull_qset = arg;
printk("SCULL_IOCHQSET: scull_qet = %d, and return old scull_qset = %d\n", scull_qset, tmp);
return tmp;
/*
* The following two change the buffer size for scullpipe.
* The scullpipe device uses this same ioctl method, just to
* write less code. Actually, it's the same driver, isn't it?
*/
case SCULL_P_IOCTSIZE:
scull_p_buffer = arg;
break;
case SCULL_P_IOCQSIZE:
return scull_p_buffer;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
}
/******************************************************************************/
linux ioctl命令,关于LINUX下的ioctl函数相关推荐
- linux下执行php命令echo不输出,linux echo命令以及linux echo命令提示权限不够的方法...
linux的echo命令, 在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的, 因此有必要了解下echo的用法.下面通过本文给大家介绍linux echo命令以及linux ...
- linux+echo+权限不够,解决linux echo命令以及linux echo命令提示权限不够的方法
linux的echo命令, 在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的, 因此有必要了解下echo的用法.下面通过本文给大家介绍linux echo命令以及linux ...
- linux mv命令的功能,linux常用命令:Linux常用命令之mv命令是什么?
Linux是目前使用比较广泛的服务器操作系统,可以安装在各种计算机硬件设备中,例如手机,路由器等.由于Linux是开源和免费的,因此Linux系统的稳定性和安全性更高一些,所以Linux具有很高的市场 ...
- linux 关机命令总结,Linux关机命令总结
在linux命令中reboot是重新启动,shutdown -r now是立即停止然后重新启动,都说他们两个是一样的,其实是有一定的区别的. shutdown命令可以安全地关闭或重启Linux系统,它 ...
- linux clock命令,Centos Linux下使用date/clock/hwclock命令设置系统和硬件时间
博客迁移之后,没有注意新服务器的系统时间,今天突然感觉到不对,于是调了一下: 设置时间为2010年06月17日,命令如下 1 2 [root@hexuweb101~]$date-s06/17/2010 ...
- linux压缩命令讲解,Linux下的压缩解压缩命令详解
linux zip命令 zip -r myfile.zip ./* 将当前目录下的所有文件和文件夹全部压缩成myfile.zip文件,-r表示递归压缩子目录下所有文件. 2.unzip unzip - ...
- linux ps命令大全,Linux ps命令例子汇总
Linux ps命令主要用于查看系统运行的进程,确定进程运行的状态机是否占用过多资源等?下面学习啦小编通过实例来给大家详细介绍下Linux的ps命令,一起来了解下吧. Linux提供了当前进程的同时, ...
- Linux文件管理命令vi,linux人云亦云(16)文件管理之VI命令一
vi或vim是linux下非常强大,使用非常普遍的一个全屏幕文本编辑器,vim是vi的高级版本. 多数的linux操作系统都自带vi编辑器.可见其强大与普遍. 下面我们就来学习一下这个神秘的文本年编辑 ...
- linux man命令无效,Linux man命令的具体使用
01. 命令概述 Linux提供了丰富的帮助手册,当你需要查看某个命令的参数时不必到处上网查找,只要man一下即可. 同时也可以使用man man 查看man的使用方法. 02. 命令格式 man [ ...
最新文章
- OpenAI推新程序包:GPU适应十倍大模型仅需增加20%训练时间
- java压缩/解压缩zip格式文件
- android sha1和签名证书的学习
- 一起围观下我们CTO写的代码,巧妙使用枚举干掉if-else!
- Linux中安装WPS
- u8系统怎么连接服务器,u8客户端连接服务器流程
- EasyX入门和介绍
- HDU 2448 最短路+KM匹配
- 协议基础:SMTP:使用Telnet学习SMTP协议
- Python 高级变量类型 —— 列表、元组、字典、字符串、公共方法
- 手机ANR问题处理方法及策略
- 2022-09-08 mysql/stonedb-慢SQL-记录
- 为什么说现在IT toB发展的拐点
- ImageJ如何测量物体的面积大小
- bff Backends For Frontends 聚合层
- aion单机服务器维护中,永恒之塔(AION)单机版架设图文教程
- Unity 2D游戏开发案例学习——Robble Swifthand(下)
- 【02】Java进阶:13-IO资源的处理、属性集、缓冲流、转换流、序列化、打印流、装饰设计模式、commons-io工具包
- java web 漏洞扫描工具_java编写web漏洞扫描系列 一、GET/POST
- Android平台视频相关的多媒体技术理解笔记
热门文章
- 用 html+JavaScript+CSS 写一个全屏时钟
- 解决图片上传 显示图片慢的问题
- 简易商品购物系统登陆界面
- 达梦查询一周内,一月内,一年内的数据
- 阅读软件怎么添加书源_**爱好者必备,一个软件,两个层面全覆盖!收下它
- 百度搜索词API接口,淘宝搜索词API接口
- 计算机英语司爱侠第二版全文,计算机英语 第2版 配套习题 作者 司爱侠 张强华 参考试卷.doc...
- ubuntu 编译 jdk (二)
- 医疗行业医院内部信息大数据分析
- 遗传算法类OX交叉选择算法的python实现