MTK FLASHLIGHT学习

内核中常见的创建文件方法
文件权限定义 S_IRUSR等
强大的DEVICE_ARRT宏
DEVICE_ATTR和DEVICE_CREATE_FILE使用
内核中常见基本调用函数

/*** pinctrl_lookup_state() - retrieves a state handle from a pinctrl handle* @p: the pinctrl handle to retrieve the state from* @name: the state name to retrieve*/
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name)
/*** pinctrl_select_state() - select/activate/program a pinctrl state to HW* @p: the pinctrl handle for the device that requests configuration* @state: the state handle to select/activate/program*/
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
/*** struct devm_pinctrl_get() - Resource managed pinctrl_get()* @dev: the device to obtain the handle for** If there is a need to explicitly destroy the returned struct pinctrl,* devm_pinctrl_put() should be used, rather than plain pinctrl_put().*/
struct pinctrl *devm_pinctrl_get(struct device *dev)/** list_for_each_entry_safe - iterare over list of given types
* @pos:        the type * to use as a loop cursor
* @head:       the head for your list
* @member: the name of the list_head within the struct
*/
list_for_each_entry(pos, head, member)相比于list_for_each_entry,list_for_each_entry_safe用指针n对链表的下一个数据结构进行了临时存储,所以如果在遍历链表
的时候需要做删除链表中的当前项操作时,用list_for_each_entry_safe可以安全的删除,而不会影响接下来的遍历过程(用n指针
可以继续完成接下来的遍历, 而list_for_each_entry则无法继续遍历,删除后会导致无法继续遍历)/** list_for_each_entry_safe - iterare over list of given types safe against removal of list entry
* @pos:        the type * to use as a loop cursor
* @n:      another type * to use as temporary storage
* @head:       the head for your list
* @member: the name of the list_head within the struct
*/
list_for_each_entry_safe(pos, n, head, member)

判断返回指针是否错误的内联函数
  linux内核中判断返回指针是否错误的内联函数主要有:ERR_PTR、PTR_ERR、IS_ERR和IS_ERR_OR_NULL等
  理解IS_ERR(),首先理解要内核空间。所有的驱动程序都是运行在内核空间,内核空间虽然很大,但总是有限的,而在这有限的空间中,其最后一个page是专门保留的,也就是说一般人不可能用到内核空间最后一个page的指针。换句话说,你在写设备驱动程序的过程中,涉及到的任何一个指针,必然有三种情况:
1,有效指针;
2,NULL,空指针;
3,错误指针,或者说无效指针。
  所谓的错误指针就是指其已经到达了最后一个page,即内核用最后一页捕捉错误。比如对于32bit的系统来说,内核空间最高地址0xffffffff,那么最后一个page就是指的0xfffff000~0xffffffff(假设4k一个page),这段地址是被保留的。
  内核空间为什么留出最后一个page?我们知道一个page可能是4k,也可能是更多,比如8k,但至少它也是4k,所以留出一个page出来就可以让我们把内核空间的指针来记录错误了。内核返回的指针一般是指向页面的边界(4k边界),即ptr & 0xfff == 0。如果你发现你的一个指针指向这个范围中的某个地址,那么你的代码肯定出错了。
  IS_ERR( )就是判断指针是否有错,如果指针并不是指向最后一个page,那么没有问题;如果指针指向了最后一个page,那么说明实际上这不是一个有效的指针,这个指针里保存的实际上是一种错误代码。
  通常很常用的方法就是先用IS_ERR()来判断是否是错误,然后如果是,那么就调用PTR_ERR()来返回这个错误代码。
  ERR_PTR、PTR_ERR只是对错误进行转换,即返回的是指针的地址。
  IS_ERR_OR_NULL是判断指针是空指针或是错误指针,即上述2和3类型的指针

#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
//注意这里的unlikely意思是括号内的值很有可能是假
static inline void *ERR_PTR(long error){return (void *) error;
}
static inline long PTR_ERR(const void *ptr){return (long) ptr;
}
static inline long IS_ERR(const void *ptr){return IS_ERR_VALUE((unsigned long)ptr);
}

< paltform > / < Project >.dts 中加入picntrl的定义以及驱动注册节点

flashlights_led191:flashlights_led191{compatible = "mediatek,flashlights_led191";decouple = <1>;channel@1{type = <0>;ct = <0>;part = <0>;        };
};
&pio {flash_default: flash_default_cfg {};flash_en_pin0: flash_en_pin0 {pins_cmd_dat {pinmux = <PINMUX_GPIO153__FUNC_GPIO153>;slew-rate = <1>;output-low;};};flash_en_pin1: flash_en_pin1 {pins_cmd_dat {pinmux = <PINMUX_GPIO153__FUNC_GPIO153>;slew-rate = <1>;output-high;};};torch_en_pin0: torch_en_pin0 {pins_cmd_dat {pinmux = <PINMUX_GPIO152__FUNC_GPIO152>;slew-rate = <1>;output-low;};};torch_en_pin1: torch_en_pin1 {pins_cmd_dat {pinmux = <PINMUX_GPIO152__FUNC_GPIO152>;slew-rate = <1>;output-high;};};
};
&flashlights_led191 {pinctrl-names = "flash_default", "flash_en_pin0", "flash_en_pin1", "torch_en_pin0", "torch_en_pin1";pinctrl-0 = <&flash_default>;pinctrl-1 = <&flash_en_pin0>;pinctrl-2 = <&flash_en_pin1>;pinctrl-3 = <&torch_en_pin0>;pinctrl-4 = <&torch_en_pin1>;status = "okay";
};

pinctrl的功能在具体的驱动功能中主要体现在flashlights-led191.c

#define LED191_PINCTRL_PINSTATE_LOW 0
#define LED191_PINCTRL_PINSTATE_HIGH 1
#define LED191_PINCTRL_STATE_HW_CH0_HIGH "flash_en_pin1"
#define LED191_PINCTRL_STATE_HW_CH0_LOW  "flash_en_pin0"
#define LED191_PINCTRL_STATE_HW_CH1_HIGH "torch_en_pin1"
#define LED191_PINCTRL_STATE_HW_CH1_LOW  "torch_en_pin0"static struct pinctrl *led191_pinctrl;
static struct pinctrl_state *led191_hw_ch0_high;
static struct pinctrl_state *led191_hw_ch0_low;
static struct pinctrl_state *led191_hw_ch1_high;
static struct pinctrl_state *led191_hw_ch1_low;
//probe的过程中
static int led191_probe(struct platform_device *pdev){struct led191_platform_data *pdata = dev_get_platdata(&pdev->dev);//dev_get_platform即获取platform_data结构体的内容int err;int i;int ret;pr_debug("Probe start.\n");/* init pinctrl */if (led191_pinctrl_init(pdev)) {pr_debug("Failed to init pinctrl.\n");err = -EFAULT;goto err;}/* init platform data */if (!pdata) {pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);//devm_xx都是和设备资源管理(Managed Device source)相关,提供函数主要是为了方便//对申请资源进行释放,在驱动进行初始化失败时,会goto到某个地方释放资源,这样的标签//多了之后会让代码看起来不简洁,devm就是处理这种情况,一般主要有两个流程//(1)向dev注册release函数  (2)驱动注册失败时调用release函数 所以devm_kzalloc当设备//被拆除或卸载时,申请的内存会被自动释放//kzalloc实现了kmallc+memset的功能if (!pdata) {err = -ENOMEM;goto err;}pdev->dev.platform_data = pdata;//解析设备树dtserr = led191_parse_dt(&pdev->dev, pdata);if (err)goto err;}.../* clear usage count */use_count = 0;/* register flashlight device */if (pdata->channel_num) {for (i = 0; i < pdata->channel_num; i++)if (flashlight_dev_register_by_device_id(&pdata->dev_id[i],&led191_ops)) {err = -EFAULT;goto err;}} else {if (flashlight_dev_register(LED191_NAME, &led191_ops)) { //没有使用dtserr = -EFAULT;goto err;}}
#ifdef FLASH_NODEret = device_create_file(&pdev->dev, &dev_attr_led_flash);if(ret < 0)pr_err("=== create led_flash_node file failed ===\n");
err:return err;
}#ifdef FLASH_NODE
static int led_flash_state = 0;
static ssize_t led_flash_show(struct device *dev, struct device_attribute *attr, char *buf){return sprintf(buf, "%d\n", led_flash_state);
}
static ssize_t led_flash_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size){unsigned long value;int err;err = kstrtoul(buf, 10, &value);//自动转换为整型if(err != 0){return err;}switch(value){case 0:g_flash_channel_idx = 1;err = led191_pinctrl_set(0, 0);if(err < 0)PK_ERR("AAA - error1 - AAA\n");led_flash_state = 0;break;case 1:g_flash_channel_idx = 1;err = led191_pinctrl_set(0, 1);if(err < 0)PK_ERR("AAA - error1 - AAA\n");led_flash_state = 1;break;default :break;}return 1;
}
static DEVICE_ATTR(led_flash, 0664, led_flash_show, led_flash_store);
#endif
//对应路径 /sys/bus/platform/devices/flashlights_led191  可通过echo去打开闪光灯static int led191_parse_dt(struct device *dev,struct led191_platform_data *pdata){struct device_node *np, *cnp;u32 decouple = 0;int i = 0;if (!dev || !dev->of_node || !pdata)return -ENODEV;np = dev->of_node;//of_node 将 devce 转换成 device_node结构体//dev 是 platform_device中定义的结构体pdata->channel_num = of_get_child_count(np);//算出此节点下面有多少个节点if (!pdata->channel_num) {pr_info("Parse no dt, node.\n");return 0;}pr_info("Channel number(%d).\n", pdata->channel_num);if (of_property_read_u32(np, "decouple", &decouple))//读取np属性名为decouple的u32的值pr_info("Parse no dt, decouple.\n");pdata->dev_id = devm_kzalloc(dev,pdata->channel_num *sizeof(struct flashlight_device_id),GFP_KERNEL);//一个通道申请一个id的空间if (!pdata->dev_id)return -ENOMEM;for_each_child_of_node(np, cnp) {//遍历所有子节点 np是root  cnp是childif (of_property_read_u32(cnp, "type", &pdata->dev_id[i].type))goto err_node_put;if (of_property_read_u32(cnp, "ct", &pdata->dev_id[i].ct))goto err_node_put;if (of_property_read_u32(cnp, "part", &pdata->dev_id[i].part))goto err_node_put;snprintf(pdata->dev_id[i].name, FLASHLIGHT_NAME_SIZE,LED191_NAME);//若字符串长度小于size,全部复制+"\0",若大于等于则复制(size-1)+'\0'pdata->dev_id[i].channel = i;pdata->dev_id[i].decouple = decouple;i++;}return 0;
err_node_put:of_node_put(cnp);//对对象的引用数加1return -EINVAL;
}
static int led191_pinctrl_init(struct platform_device *pdev){int ret = 0;/* get pinctrl */led191_pinctrl = devm_pinctrl_get(&pdev->dev);//devm_pinctrl_get获取pinctrl句柄,各状态都与其绑定if (IS_ERR(led191_pinctrl)) {pr_err("Failed to get flashlight pinctrl.\n");ret = PTR_ERR(led191_pinctrl);}/*  Flashlight pin initialization */led191_hw_ch0_high = pinctrl_lookup_state(led191_pinctrl, LED191_PINCTRL_STATE_HW_CH0_HIGH);//从led1_pinctrl中获取状态句柄led191_hw_ch0_hight static struct pinctrl_state *led191_hw_ch0_high;if (IS_ERR(led191_hw_ch0_high)) {PK_ERR("Failed to init (%s)\n", LED191_PINCTRL_STATE_HW_CH0_HIGH);ret = PTR_ERR(led191_hw_ch0_high);}led191_hw_ch0_low = pinctrl_lookup_state(led191_pinctrl, LED191_PINCTRL_STATE_HW_CH0_LOW);if (IS_ERR(led191_hw_ch0_low)) {PK_ERR("Failed to init (%s)\n", LED191_PINCTRL_STATE_HW_CH0_LOW);ret = PTR_ERR(led191_hw_ch0_low);}led191_hw_ch1_high = pinctrl_lookup_state(led191_pinctrl, LED191_PINCTRL_STATE_HW_CH1_HIGH);if (IS_ERR(led191_hw_ch1_high)) {PK_ERR("Failed to init (%s)\n", LED191_PINCTRL_STATE_HW_CH1_HIGH);ret = PTR_ERR(led191_hw_ch1_high);}led191_hw_ch1_low = pinctrl_lookup_state(led191_pinctrl, LED191_PINCTRL_STATE_HW_CH1_LOW);if (IS_ERR(led191_hw_ch1_low)) {PK_ERR("Failed to init (%s)\n", LED191_PINCTRL_STATE_HW_CH1_LOW);ret = PTR_ERR(led191_hw_ch1_low);}return ret;
}
static int led191_pinctrl_set(int pin, int state){int ret = 0;if (IS_ERR(led191_pinctrl) || IS_ERR(led191_hw_ch0_high) || IS_ERR(led191_hw_ch0_low) || IS_ERR(led191_hw_ch1_high) || IS_ERR(led191_hw_ch1_low)) {pr_err("pinctrl is not available\n");return -1;}switch (pin) {case 0://torch modeif (state) {//enableret = pinctrl_select_state(led191_pinctrl, led191_hw_ch0_low);//153-0 ch1 torchenret += pinctrl_select_state(led191_pinctrl, led191_hw_ch1_high);//152-1} else {//disableret = pinctrl_select_state(led191_pinctrl, led191_hw_ch0_low);ret += pinctrl_select_state(led191_pinctrl, led191_hw_ch1_low);}break;case 1://flash modeif (state) {//enableret = pinctrl_select_state(led191_pinctrl, led191_hw_ch0_high);ret += pinctrl_select_state(led191_pinctrl, led191_hw_ch1_low);} else {//disableret = pinctrl_select_state(led191_pinctrl, led191_hw_ch0_low);ret += pinctrl_select_state(led191_pinctrl, led191_hw_ch1_low);}break;default:pr_err("set err, pin(%d) state(%d)\n", pin, state);break;}pr_debug("pin(%d) state(%d), ret:%d\n", pin, state, ret);return ret;
}static int led191_init(void){int pin = 0;int state = 0;return led191_pinctrl_set(pin, state);
}
alps/kernel-4.4/drivers/misc/mediatek/flashlight/flashlight-core.h
/* device operations */
struct flashlight_operations {int (*flashlight_open)(void);int (*flashlight_release)(void);int (*flashlight_ioctl)(unsigned int cmd, unsigned long arg);ssize_t (*flashlight_strobe_store)(struct flashlight_arg arg);int (*flashlight_set_driver)(int set);
};
alps/kernel-4.4/drivers/misc/mediatek/flashlight/flashlights-led191.c
static struct flashlight_operations led191_ops = {led191_open,led191_release,led191_ioctl,led191_strobe_store,led191_set_driver
};

flashlight_core和flashlights_led191的注册调用过程
  首先将flashlight_core和flashlights_led191都会被注册到platform总线上,通过节点匹配后调用probe函数,
  flashlight_core在probe过程中向内核申请了主次设备号、sysfs下注册了flashlight类、创建操作的文件等(如上);
  flashlights_led191在probe的过程中则是对设备数进行解析,并通过flashlight_dev_register_by_device_id(如下)中list_add_tail在链表flashlight_list前面添加了具体的设备led191,并赋值了led191_ops(file_operations),所以flashlight_open的过程中,通过遍历链表fdev->ops->flashlight_open()调用的就是flashlights_led191.c中的led191_open

/* alps/kernel-4.4/drivers/misc/mediatek/flashlight/flashlight-core.c* Register devices* * Please DO NOT register flashlight device driver,* until success to probe hardware.*/
int flashlight_dev_register_by_device_id(struct flashlight_device_id *dev_id,struct flashlight_operations *dev_ops){struct flashlight_dev *fdev;if (!dev_id || !dev_ops)return -EINVAL;if (flashlight_verify_index(dev_id->type, dev_id->ct, dev_id->part)) {pr_err("Failed to register device (%d,%d,%d)\n",dev_id->type, dev_id->ct, dev_id->part);return -EINVAL;}pr_info("Register device (%d,%d,%d)\n", dev_id->type, dev_id->ct, dev_id->part);mutex_lock(&fl_mutex);fdev = kzalloc(sizeof(*fdev), GFP_KERNEL);if (!fdev) {mutex_unlock(&fl_mutex);return -ENOMEM;}fdev->ops = dev_ops;fdev->dev_id = *dev_id;fdev->low_pt_level = -1;fdev->charger_status = FLASHLIGHT_CHARGER_READY;list_add_tail(&fdev->node, &flashlight_list);mutex_unlock(&fl_mutex);return 0;
}
EXPORT_SYMBOL(flashlight_dev_register_by_device_id);  //内核用dev_t来保持设备编号
//MAJOR(flashlight_devno) MINOR(flashlight_devno) 取主次设备号
static dev_t flashlight_devno;
static struct cdev *flashlight_cdev;
static struct class *flashlight_class;
static struct device *flashlight_device;#define FLASHLIGHT_DEVNAME          "flashlight"
#define FLASHLIGHT_CORE             "flashlight_core"const struct flashlight_device_id flashlight_id[] = {/* {TYPE, CT, PART, "NAME", CHANNEL, DECOUPLE} */{0, 0, 0, "flashlights_led191", 0, 0},
};static DEFINE_MUTEX(fl_mutex);
LIST_HEAD(flashlight_list);flashlight_probealloc_chrdev_region(&flashlight_devno, 0, 1, FLASHLIGHT_DEVNAME);//向内核动态申请一个主次设备号,与register_chrdev_region(指定)不同flashlight_cdev = cdev_alloc();//申请内存,并对内存进行memset清零flashlight_cdev->ops = &flashlight_fops;flashlight_cdev->owner = THIS_MODULE;cdev_add(flashlight_cdev, flashlight_devno, 1)//添加字符设备 flashlight_class = class_create(THIS_MODULE, FLASHLIGHT_CORE);//创建类flashlight_device = device_create(flashlight_class, NULL, flashlight_devno,NULL, FLASHLIGHT_DEVNAME);//创建设备节点device_create_file(flashlight_device, &dev_attr_flashlight_strobe)device_create_file(flashlight_device, &dev_attr_flashlight_pt)device_create_file(flashlight_device, &dev_attr_flashlight_charger)device_create_file(flashlight_device, &dev_attr_flashlight_capability)device_create_file(flashlight_device, &dev_attr_flashlight_fault)device_create_file(flashlight_device, &dev_attr_flashlight_sw_disable)//添加SYSFS设备文件static DEVICE_ATTR_RW(flashlight_pt);对文件的操作函数 fl_init();//空  return 0static DEVICE_ATTR_RW(flashlight_pt)      //除此之外还有DRIVER_ATTR,BUS_ATTR,CLASS_ATTR,对应sys下不同的目录
/* pt status sysfs */
static ssize_t flashlight_pt_show(...)     //设备文件操作的cat
static ssize_t flashlight_pt_store(...)    //设备文件操作的echoflashlight_removefl_uninit();struct flashlight_dev *fdev, *n;mutex_lock(&fl_mutex);list_for_each_entry_safe(fdev, n, &flashlight_list, node) {//对链表进行编译/* uninit device */if (fdev->ops) {fdev->ops->flashlight_open();fdev->ops->flashlight_set_driver(1);led191_init();fl_enable(fdev, 0);fl_dev_arg.arg = enable;fdev->ops->flashlight_ioctl(FLASH_IOC_SET_ONOFF,(unsigned long)&fl_dev_arg)led191_disable();fdev->ops->flashlight_set_driver(0);led191_uninit();fdev->ops->flashlight_release();}/* clear node and free memory */list_del(&fdev->node);kfree(fdev);}mutex_unlock(&fl_mutex);device_remove_file(flashlight_device, &dev_attr_flashlight_sw_disable);device_remove_file(flashlight_device, &dev_attr_flashlight_fault);device_remove_file(flashlight_device, &dev_attr_flashlight_current);device_remove_file(flashlight_device, &dev_attr_flashlight_capability);device_remove_file(flashlight_device, &dev_attr_flashlight_charger);device_remove_file(flashlight_device, &dev_attr_flashlight_pt);device_remove_file(flashlight_device, &dev_attr_flashlight_strobe);/* remove device */device_destroy(flashlight_class, flashlight_devno);/* remove class */class_destroy(flashlight_class);/* remove char device */cdev_del(flashlight_cdev);/* unregister char device number */unregister_chrdev_region(flashlight_devno, 1);flashlight_openstruct flashlight_dev *fdev;mutex_lock(&fl_mutex);list_for_each_entry(fdev, &flashlight_list, node) {if (!fdev->ops)continue; pr_debug("Open(%d,%d,%d)\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part);fdev->ops->flashlight_open();}mutex_unlock(&fl_mutex);flashlight_releasestruct flashlight_dev *fdev;mutex_lock(&fl_mutex);list_for_each_entry(fdev, &flashlight_list, node) {if (!fdev->ops)continue;pr_debug("Release(%d,%d,%d)\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part);fl_enable(fdev, 0);fdev->ops->flashlight_release();}mutex_unlock(&fl_mutex);

/********************************************************************
后续补充GPIO改为PWM模式,以及使用wakelock保证手电筒不会在休眠模式下被关闭

pwm的频率是指每秒钟信号从高电平到低电平再回到高电平的次数,
占空比是高电平持续时间和低电平持续时间之间的比例。
pwm的频率越高,其对输出的响应就会越快,频率越低输出响应越慢。
pwm的调节作用来源于对“占周期”的宽度控制,“占周期”变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压也会上升,“占周期”变窄,输出的能量就会降低,通过阻容变换电路所得到的平均电压也会下降。pwm就是通过这种原理实现D/A转换的。

Android MTK Flashlight学习相关推荐

  1. Android Qcom Flashlight学习

    pmic闪光灯电流控制的逻辑时从Hal下发电流,在kernel中做判断并工作 Hal 从下面的一段逻辑可以很清晰的得出,当没有tunning参数时,使用的时默认default 300mA,当tunni ...

  2. android mtk平台,总结自己在android MTK平台的学习

    受老罗的影响,由于本人还是菜鸟,不能像老罗一样重头开始研究整个系统,决定从就近的工作开始,从android MTK 的驱动-->中间层-->应用层,一步一步研究. 一边看书,一边搜集网上的 ...

  3. Android编译及编译脚本、Android构建基础学习笔记

    Android编译及编译脚本.Android构建基础学习笔记 Android编译及编译脚本 概述 Android.mk转换成Android.bp 例子(简单Android.mk文件转Android.b ...

  4. 基于 Android NDK 的学习之旅-----资源释放

    基于 Android NDK 的学习之旅-----资源释放 做上一个项目的时候因为与C引擎交互频繁,有时候会突然莫名其妙的的整个应用程序直接挂掉.因为我是学Java 开始的,所以对主动释放内存没多大概 ...

  5. 基于 Android NDK 的学习之旅----- C调用Java

    2019独角兽企业重金招聘Python工程师标准>>> 基于 Android NDK 的学习之旅----- C调用Java 许多成熟的C引擎要移植到Android 平台上使用 , 一 ...

  6. 基于 Android NDK 的学习之旅-----数据传输二(引用数据类型)(附源码)

    基于 Android NDK 的学习之旅-----数据传输(引用数据类型) 接着上篇文章继续讲.主要关于引用类型的数据传输,本文将介绍字符串传输和自定义对象的传输. 1.主要流程 1.  String ...

  7. 【转】基于 Android NDK 的学习之旅-----数据传输(引用数据类型)

    原文网址:http://www.cnblogs.com/luxiaofeng54/archive/2011/08/20/2147086.html 基于 Android NDK 的学习之旅-----数据 ...

  8. 基于android的交流平台,基于Android的移动学习交流平台的设计与实现

    摘要: 随着移动互联网技术的不断发展,智能手机的不断普及,现在越来越多的人通过手机等智能设备来进行学习和交流.为了满足教师和学生实时的沟通交流,提高学生的学习兴趣和效率,本文设计了基于Android的 ...

  9. Android基础知识学习

    一.Android编译过程 初始化参数设置 检查环境变量与目标环境 选择lunch并读取目标配置和平台信息 清空输出目录 编译 生成升级包 二. ./build/envsetup.sh分析 加载编译命 ...

最新文章

  1. PointRCNN: 点云的3D目标生成与检测
  2. java comparable接口作用_Java 中 Comparable 接口的意义和用法
  3. HackTheGame 攻略 - 第三关
  4. 存在于实数域的微观粒子4-能量可以转变为物质
  5. leetcode1046. 最后一块石头的重量(堆)
  6. Forever让NodeJS应用后台执行
  7. 用php做盒子模型,什么是CSS盒子模型?一文带你了解CSS盒子模型
  8. sort()、stable_sort()、partial_sort()、nth_element()、greater()、is_sorted()
  9. javascript 模拟滚动 隐藏滚动条
  10. 区块链在切实改变世界的35个让人惊艳的实例数据库
  11. 扫二维码登录的实现原理
  12. 三.minio 的分布式部署、单节点多磁盘、多节点模式
  13. 采购员小刘与费用报销的“相爱相杀”史
  14. 写作技巧~100段作文排比句(1-20段),考试一定用得上,赶紧收藏!
  15. 通信网络与IP网络底层传输技术梳理(SONET/SDH/OTN/ATM/Ethernet/MPLS/PTN...)
  16. Linux I/O重定向 dup dup2 系统调用
  17. 用python编写一个点餐程序_急急急!求大神帮忙做个Python在线点餐小软件
  18. 帝国时代2哪个服务器稳定,《帝国时代2决定版》新手该选择哪个国家|新手使用国家推荐...
  19. 罗技c270摄像头支持linux,罗技C270摄像头的使用心得
  20. 数量遗传学 第三章 Hardy -Weinberg Weinberg 法则及应用

热门文章

  1. 最受商户关注的五大进销存软件,这份贴心排行榜秘籍请收好
  2. 程序员初入职场,如何规划好自己的职业生涯?
  3. mysql 创建 unique key_MySQL-创建表时一起使用时,“ PRIMARY KEY”,“ UNIQUE KEY”和“ KEY”的含义...
  4. 如何重新发明短信息这个古老的轮子
  5. 点亮技能 I 人机对话系统全面理解
  6. centos7搭建http代理ip TinyProxy 及验证是否有效(python)
  7. vue开发APP使用微信分享和QQ分享功能
  8. dedecms织梦后台登录一直提示验证码错误
  9. 基于Java Swing界面编程教学
  10. HTML5属性选择器以什么开头,CSS3 选择器 属性选择器介绍