Linux 驱动框架---linux 设备
Linux 设备
Linux驱动中的三大主要基础成员主要是设备,总线和驱动。今天先来从设备开始分析先把设备相关的数据结构放到这里方便后面看到来查,其中有些进行了简单的注释。
struct device {
struct device *parent;/*父设备*/ struct device_private *p;/*设备私有数据 包含子设备,父设备,总线等的链表*/ struct kobject kobj;/*内核对象*/
const char *init_name; /* initial name of the device */
const struct device_type *type; /* 设备类型抽象共同的部分*/ struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/ struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain; #ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif #ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset; struct device_dma_parameters *dma_parms; struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata; struct device_node *of_node; /* associated device tree node */
struct acpi_dev_node acpi_node; /* associated ACPI device node */ dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */ spinlock_t devres_lock;
struct list_head devres_head; struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev);
struct iommu_group *iommu_group; bool offline_disabled:1;
bool offline:1;
};
有了设备就需要考虑设备创建和加入的部分了,Linux内核不使用设备树时设备是板级文件创建并注册添加的,使用设备树后就是由系统启动从设备树中解析出来并动态创建的。
设备创建
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
上面这个函数实际内部调用是
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev; va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
设备的创建有两种情况通过device 提供的接口 device_create 创建设备,此时的设备是通过动态申请内存创建的,创建的时候可以种指定设备类class,父设备parent,设备携带的驱动数据drvdata等除此之外还需要提供设备号dev_t,这个接口最终会返回一个struct device 的设备句柄。只要是通过这种方式创建的设备这个设备中的release接口就会自动绑定一个释放这个设备所占内存的方法。
static void device_create_release(struct device *dev)
{
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
kfree(dev);
} static struct device *
device_create_groups_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const struct attribute_group **groups,
const char *fmt, va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV; if (class == NULL || IS_ERR(class))
goto error; dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
} device_initialize(dev);
dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->groups = groups;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata); retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error; retval = device_add(dev);
if (retval)
goto error; return dev; error:
put_device(dev);
return ERR_PTR(retval);
}
这个接口创建的设备他的init_name 字段是未初始化的,之后这个接口的后续会调用device_add(这个接口内会检查device init_name是否设置,如果有则会将其设置给device name字段,然后会将device init_name置空;反之如果没有指定这个字段则系统将按照总线名称和设备号的方式给他一个name,总之设备不能没有未初始化名称然后添加到系统中)将设备加入到指定的总线。第二种是静态创建常见包含在platform_device中在芯片的板级文件中静态创建platform_device设备的时候创建,具体可以platform_device的结构体定义就能发现其中定义了一个设备实体成员而非句柄。
由上面可以看出实际是device_create -->device_create_vargs这个函数里面最后会调用device的添加函数(device_add())将新创建的设备添加到内核。
设备注册添加
设备创建好并初始化好后需要注册添加进内核,一共有两种情况下将设备添加进内核。第一种就是上面的提到的设备创建和添加一起由创建的内部完成就是通过device_add接口,还有一种就是device_register,他的调用过程大致如下面的过程。因为很多地方有时候是调用的注册接口(device_register)有时调用的是接口(device_add),所以这里就顺带把这个接口也拿出来说一下区分一下区别。直接看代码就很直观了
device_register
1 int device_register(struct device *dev)
2 {
3 device_initialize(dev);
4 return device_add(dev);
5 }
调用-->device_initialize() 初始化必要的成员后最终也是调用device_add将设备添加进内核。接下来开始细看device_add 过程(关键)。在这之前需要明确在Linux中设备都是挂在总线下的。没有总线的设备没有意义。下面就是device_add接口的内部实现,加了一些注释,异常处理删除了。
1 int device_add(struct device *dev)
2 {
3 struct device *parent = NULL;
4 struct kobject *kobj;
5 struct class_interface *class_intf;
6 int error = -EINVAL;
7 /* 增加设备的引用次数 */
8 dev = get_device(dev);
9 if (!dev)
10 goto done;
11 /* 增加设备私有数据 私有数据包括指向当前device自己的指针和一个list用来链接设备下的子设备 */
12 if (!dev->p) {
13 error = device_private_init(dev);
14 if (error)
15 goto done;
16 }
17
18 /* Kobject 的设置 */
19 if (dev->init_name) {
20 dev_set_name(dev, "%s", dev->init_name);
21 dev->init_name = NULL;
22 }
23
24 /* 设备name如果未设置 则按 %s%u,总线name,device->id形式格式化*/
25 if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
26 dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
27 /* 没有设备名的设备是不允许添加到系统的 */
28 if (!dev_name(dev)) {
29 error = -EINVAL;
30 goto name_error;
31 }
32
33 pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
34 /* 增加父设备引用计数 */
35 parent = get_device(dev->parent);
36 /* 参考 https://blog.csdn.net/qq_20678703/article/details/52846064 */
37 kobj = get_device_parent(dev, parent);
38 if (kobj)
39 dev->kobj.parent = kobj;
40
41 /* use parent numa_node */
42 if (parent)
43 set_dev_node(dev, dev_to_node(parent));
44
45 /* first, register with generic layer. */
46 /* we require the name to be set before, and pass NULL */
47 /* kobject 操作 可以参考kobject的相关部分 */
48 error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
49 if (error)
50 goto Error;
51
52 /* notify platform of device entry */
53 if (platform_notify)
54 platform_notify(dev);
55 /* sysfs操作 创建设备通用属性的文件虚拟文件系统相关*/
56 error = device_create_file(dev, &dev_attr_uevent);
57 if (error)
58 goto attrError;
59 /* sysfs下创建虚拟文件 */
60 if (MAJOR(dev->devt)) {
61 error = device_create_file(dev, &dev_attr_dev);
62 if (error)
63 goto ueventattrError;
64 error = device_create_sys_dev_entry(dev);
65 if (error)
66 goto devtattrError;
67
68 devtmpfs_create_node(dev);
69 }
70 /* 实际的设备按不同的分类可能同属多个分类父设备 但是真实的设备在文件系统中只要
71 一份而其他的则是以符号链接的形式指向唯一的实体 */
72 error = device_add_class_symlinks(dev);
73 if (error)
74 goto SymlinkError;
75 /* sysfs操作 创建设备个性属性的文件*/
76 error = device_add_attrs(dev);
77 if (error)
78 goto AttrsError;
79 /* 根据device 中的bus指针将设备添加到特定的总线*/
80 error = bus_add_device(dev);
81 if (error)
82 goto BusError;
83 /* 电源管理相关 */
84 error = dpm_sysfs_add(dev);
85 if (error)
86 goto DPMError;
87 /* 将设备加入到了一个list 中 电源管理子系统?*/
88 device_pm_add(dev);
89
90 /* Notify clients of device addition. This call must come
91 * after dpm_sysfs_add() and before kobject_uevent().
92 */
93 /* 一种客户端新设备发现机制 通知客户端新设备添加 */
94 if (dev->bus)
95 blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
96 BUS_NOTIFY_ADD_DEVICE, dev);
97 /* kobject的事件机制 产生uevent事件 事件类型为ADD
98 * 内核可以通过helper或netlink机制和用户空间的应用通信,热插拔机制
99 */
100 kobject_uevent(&dev->kobj, KOBJ_ADD);
101 /*
102 * 驱动开始匹配,后面详细分析
103 */
104 bus_probe_device(dev);
105 /* 设备私有数据 将设备添加到父设备子设备list中*/
106 if (parent)
107 klist_add_tail(&dev->p->knode_parent,
108 &parent->p->klist_children);
109 /* 将设备添加到所属class设备子设备list中*/
110 if (dev->class) {
111 mutex_lock(&dev->class->p->mutex);
112 /* tie the class to the device */
113 klist_add_tail(&dev->knode_class,
114 &dev->class->p->klist_devices);
115
116 /* */
117 /* notify any interfaces that the device is here */
118 list_for_each_entry(class_intf,
119 &dev->class->p->interfaces, node)
120 if (class_intf->add_dev)
121 class_intf->add_dev(dev, class_intf);
122 mutex_unlock(&dev->class->p->mutex);
123 }
124 done:
125 put_device(dev);
126 return error;
127 。
128 。
129 。
130 。
131 。
132 。
133 }
从这个函数我们了解了设备添加的大致过程:
- device结构体中的struct device_private *p 实际上就是设备私有数据用来管理设备相关的子设备list,总线等信息。
- device 中的init_name 字段实际上最后是给到了内核对象kobject,如果未指定device的init_name则会按照总线和设备ID的方式构造device的name。
- 没有名字的设备是不允许添加到系统的。
- 最后就是kobject的初始化和添加了,这一部分可以参考kobject的实现。
- 大多数的设备虚拟文件系统中的文件创建和设备是否匹配到驱动无关,至于/dev目录下的还是个疑问。
- uevent 设备事件产生。
- 驱动探测的成功和失败的结果不影响设备的注册添加,因为驱动可以晚于设备注册。
- 是否自动进行驱动匹配取决于系统的总系的属性。
中间的bus_probe_device就是开始探测驱动的过程。
/*
* 驱动匹配
*/
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
struct subsys_interface *sif;
int ret; if (!bus)
return;
//是否自动探测
if (bus->p->drivers_autoprobe) {
ret = device_attach(dev);
WARN_ON(ret < 0);
} mutex_lock(&bus->p->mutex);
list_for_each_entry(sif, &bus->p->interfaces, node)
if (sif->add_dev)
sif->add_dev(dev, sif);
mutex_unlock(&bus->p->mutex);
}
/**
*
**/
int device_attach(struct device *dev)
{
int ret = 0; device_lock(dev);
/* 如果设备已经指定驱动 则直接执行驱动和设备的绑定操作,在sys文件系统下建立对应文件 */
if (dev->driver) {
if (klist_node_attached(&dev->p->knode_driver)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
/* 需要匹配 */
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
pm_request_idle(dev);
}
out_unlock:
device_unlock(dev);
return ret;
}
分两种情况,一种是这个接口被调用仅仅是来绑定驱动和设备的;而我们这里显然不是,通过bus_for_each_drv 遍历总线上的每一个驱动和当前设备进行适配,尝试能否匹配。匹配用的接口是__device_attach这个接口代码如下,处理的过程大致是
如果总线有实现匹配match接口则调用总线的match接口传入的参数就是驱动和设备的句柄。否则调用driver_probe_device进行进一步的尝试。
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
struct klist_iter i;
struct device_driver *drv;
int error = 0; if (!bus)
return -EINVAL;
/* 遍历总线中的驱动list 挨个执行__device_attach函数 */
klist_iter_init_node(&bus->p->klist_drivers, &i,
start ? &start->p->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error)
error = fn(drv, data);
klist_iter_exit(&i);
return error;
} static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;
/* 如果总线有定义mach函数则 调用总线的mach函数 这个接口由总线提供 */
if (!driver_match_device(drv, dev))
return 0;
/* 前面的匹配未成功 进一步探测*/
return driver_probe_device(drv, dev);
}
如果总线的match接口成功匹配了就直接返回,否则继续执行 driver_probe_device接口,这个接口如下又调用了really_probe。
/* 这里会通过调用really_probe 然后执行真正的驱动的probe函数*/
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0; if (!device_is_registered(dev))
return -ENODEV; pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name); pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_request_idle(dev); return ret;
}
继续看really_probe这个接口的实现如下:
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
int local_trigger_count = atomic_read(&deferred_trigger_count); atomic_inc(&probe_count);
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));
/* 先绑定驱动和设备*/
dev->driver = drv;
/* 如果需要绑定pin 则绑定pin*/
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed;
/* 设备射驱动的sys目录下的操作 */
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
/* 如果总线提供了probe接口则执行总线的接口否则执行驱动的probe,这也
* 就解释了driver_register过程检查总线和drv的probe接口都在时会给出警告,因为他俩只能二选一且总线优先
*/
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
/* 执行到这里就是匹配成功了,所以执行剩下的驱动绑定操作,至此驱动匹配结束 */
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
/* 如果上面的操作失败了都会执行下面的操作进行开始匹配前一些操作的逆操作 */
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL); if (ret == -EPROBE_DEFER) {
/* Driver requested deferred probing */
dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
/* Did a trigger occur while probing? Need to re-trigger if yes */
if (local_trigger_count != atomic_read(&deferred_trigger_count))
driver_deferred_probe_trigger();
} else if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
} else {
pr_debug("%s: probe of %s rejects match %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
先判断总线是否实现了探测函数,是则执行总线的探测接口传入的参数为当前设备句柄,反之如果总线没有实现探测接口则执行判断驱动是否实现了探测接口是则调用传入的参数也是设备句柄。这里还暗含了一个机制如果总线提供了probe接口则执行总线的接口否则执行驱动的probe,这也就解释了driver_register过程检查总线和drv的probe接口都在时会给出警告,因为他俩只能二选一且总线优先。具体的操作都在代码中注释了。并且更加肯定在驱动和总线都有探测接口时是优先使用总线的probe接口。可以明白设备注册时指定总线对于驱动的匹配十分重要,没有总线设备添加了也无法自动探测驱动---字符设备和杂项设备是个例外因为他们好像没有强化驱动和设备的概念。
其他会使用的接口
void device_initialize(struct device *dev) 设备初始化
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
} int dev_set_name(struct device *dev, const char *fmt, ...)设置设备名称并不是init_name 字段
{
va_list vargs;
int err; va_start(vargs, fmt);
err = kobject_set_name_vargs(&dev->kobj, fmt, vargs);
va_end(vargs);
return err;
}
//device 使用计数增加,实际操作的是内涵的kobject
struct device *get_device(struct device *dev)
{
return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL;
}
device 释放一次
void put_device(struct device *dev)
{
/* might_sleep(); */
if (dev)
kobject_put(&dev->kobj);
}
设备的删除
void device_unregister(struct device *dev)
{
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
device_del(dev);
put_device(dev);
}
这里还是比较复杂一点的,因为内核中可能有和设备匹配的驱动,删除设备需要吧驱动匹配时增加的文件删除和绑定操作删除进行解绑等。将设备从系统中注销,注意和删除的区别,但是也都是设备添加过程的一些操作的逆向操作。
总结
这里总结一下设备添加的驱动匹配过程:从设备添加接口开始 bus_probe_device---》device_attach---》bus_for_each_drv 这个接口传入了一个接口函数是 __device_attach,然后bus_for_each_drv 这个接口的伪代码就是
bus_for_each_drv
{
遍历总线上的驱动drv
执行 __device_attach(drv,dev);
退出条件是 上面的接口返回非0或者总线上的drv全都尝试过了
}
__device_attach接口的执行过程大致就是先尝试总线是否有match接口有则调用,当总线的match接口返回0时则代表匹配成功则直接返回0 然后bus_for_each_drv 接口也会退出匹配完成;否则执行driver_probe_device---》really_probe 这个接口会调用总线或者驱动的probe接口进行驱动的探测且总线probe接口优先,返回0匹配成功。由此可见总线实现了match接口对于驱动匹配效率是有提升的。后续还会在去看看driver的注册添加过程,驱动和设备的添加过程有相同的部分也有不同的,但是其中驱动匹配的那一部分特别的相似,一个是拿着device遍历总线上的driver,另一个则是拿着driver去遍历device,全部都依赖总线。这就是驱动的基本架构思路,所以在Linux的发展过程中会出现platform_bus.他的出现就是为了统一linux内核的驱动框架。反向也说明platform的机制也就可以体现其他总线的一些相同机制。所以后续会在拿platform总线为实例在深入研究一下。也就能清楚设备匹配过程总线的mach接口的一般实现方式。
Linux 驱动框架---linux 设备的更多相关文章
- Linux 驱动框架---linux 驱动
总述 Linux 系统下的驱动最后都是以如下这个结构体呈现在系统中的,注意其中的dev_pm_ops是内核新增的内容来准备替换platform_driver中的电源管理相关的内容.这里内容是先进行总体 ...
- Linux 驱动框架---net驱动框架
这一篇主要是学习网络设备驱动框架性的东西具体的实例分析可以参考Linux 驱动框架---dm9000分析 .Linux 对于网络设备的驱动的定义分了四层分别是网络接口层对上是IP,ARP等网络协议,因 ...
- Linux 驱动框架---i2c驱动框架
i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开 ...
- Linux 驱动框架---input子系统框架
前面从具体(Linux 驱动框架---input子系统)的工作过程学习了Linux的input子系统相关的架构知识,但是前面的学习比较实际缺少总结,所以今天就来总结一下输入子系统的架构分层,站到远处来 ...
- Linux 驱动框架---input子系统
input 子系统也是作为内核的一个字符设备模块存在的,所以他也是字符设备自然也会有字符设备的文件接口.input子系统的注册过程主要分为两步,先注册了一个input class然后再注册一个字符设备 ...
- linux驱动之获取设备树信息
上一篇文章学习了字符设备的注册,操作过的小伙伴都知道上一篇文章中测试驱动时是通过手动创建设备节点的,现在开始学习怎么自动挂载设备节点和设备树信息的获取,这篇文章中的源码将会是我以后编写字符驱动的模板. ...
- Linux驱动框架之misc类设备驱动框架
1.何为misc设备 (1)misc中文名就是杂项设备\杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于 杂散设备,也就是misc设备,例如像a ...
- Linux 驱动框架---cdev字符设备驱动和misc杂项设备驱动
字符设备 Linux中设备常见分类是字符设备,块设备.网络设备,其中字符设备也是Linux驱动中最常用的设备类型.因此开发Linux设备驱动肯定是要先学习一下字符设备的抽象的.在内核中使用struct ...
- Linux驱动框架之framebuffer驱动框架
1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...
随机推荐
- uni-app开发经验分享十一: uniapp iOS云打包修改权限提示语
打包提交appstore如果用到了如下权限需要修改提示语,详细描述使用这个权限的原因,如不修改提示语appstore审核可能会被拒绝.Apple的原则是,如果一个app想要申请用户同意某个隐私信息访问 ...
- SpringBoot配置文件基础部分说明
SpringBoot-yml文件配置说明 友情提示:有一些代码中有乱码,请脑补删除,适合快速入门 #开启spring的Bebug模式,可以查看有哪些自动配置生效 #debug=true #开启热启动, ...
- 将ffmpeg编译为wasm版本且在浏览器中运行
2020年大前端技术趋势解读 原创 IMWeb团队 腾讯IMWeb前端团队 5天前
- https://hbase.apache.org/devapidocs/org/apache/hadoop/hbase/util/MurmurHash.html
https://hbase.apache.org/devapidocs/org/apache/hadoop/hbase/util/MurmurHash.html https://github.com/ ...
- 【rz】【sz】参数详解
参数 SYNOPSIS sz [-+8abdefkLlNnopqTtuvyY] file ... b:以二进制方式,默认为文本方式 e:对所有控制字符转义 待续 常见问题: 1.xshell 使用rz ...
- SumatraPDF设置护眼背景
高级选项中: 1 FixedPageUI [ 2 TextColor = #000000 3 BackgroundColor = #C7EDCC 4 SelectionColor = #f5fc0c ...
- 四. Ribbon负载均衡服务调用
1. 概述 1.1 Ribbon是什么 SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具. Ribbon是Netflix发布的开源项目,主要功能 ...
- loj10006数列分段
题目描述 对于给定的一个长度为 N 的正整数数列 A,现要将其分成连续的若干段,并且每段和不超过 M(可以等于 M),问最少能将其分成多少段使得满足要求. 输入格式 第一行包含两个正整数 N,M表示了 ...
- WeCenter (最新版) 前台RCE漏洞 (2020-02-22)
漏洞通过phar触发反序列化漏洞. 触发点:./models/account.php 中的 associate_remote_avatar 方法: 搜索全局调用了该方法的地方: ./app/accou ...
- Java——break,continue,return语句
break语句: break:用于改变程序控制流 用于do-while.while.for中时,可跳出循环而执行循环后面的语句. break的作用:终止当前循环语句的执行. break还可以用来终止s ...