ALSA声卡驱动的DAPM(二)-建立过程

时间:2022-01-14 15:27:56

在上一篇文章中,我们重点介绍了widget、path、route之间的关系及其widget的注册;

http://www.cnblogs.com/linhaostudy/p/8509899.html

在最后一章中,我们已经简单介绍了snd_soc_dapm_new_controls函数用来创建widget。

实际上,这个函数只是创建widget的第一步,它为每一个widget分配内存,初始化;

要使widget之间具备连接能力,我们还需要第二个函数snd_soc_dapm_new_widgets:这个函数会根据widget的信息,创建widget所需要的dapm kcontrol,这些dapm kcontol的状态变化,代表着音频路径的变化,从而影响着各个widget的电源状态。看到函数的名称可能会迷惑一下,实际上,snd_soc_dapm_new_controls的作用更多地是创建widget,而snd_soc_dapm_new_widget的作用则更多地是创建widget所包含的kcontrol,所以在我看来,这两个函数名称应该换过来叫更好!下面我们分别介绍一下这两个函数是如何工作的。

一、创建widget:snd_soc_dapm_new_controls:

snd_soc_dapm_new_controls函数完成widget的创建工作,并把这些创建好的widget注册在声卡的widgets链表中,我们看看他的定义:
 /**
* snd_soc_dapm_new_controls - create new dapm controls
* @dapm: DAPM context
* @widget: widget array
* @num: number of widgets
*
* Creates new DAPM controls based upon the templates.
*
* Returns 0 for success else error.
*/
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget,
int num)
{
struct snd_soc_dapm_widget *w;
int i;
int ret = ; mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = ; i < num; i++) {
w = snd_soc_dapm_new_control(dapm, widget);
if (!w) {
dev_err(dapm->dev,
"ASoC: Failed to create DAPM control %s\n",
widget->name);
ret = -ENOMEM;
break;
}
widget++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}

该函数只是简单的一个循环,为传入的widget模板数组依次调用snd_soc_dapm_new_control函数,实际的工作由snd_soc_dapm_new_control完成,继续进入该函数,看看它做了那些工作。

我们之前已经说过,驱动中定义的snd_soc_dapm_widget数组,只是作为一个模板,所以,snd_soc_dapm_new_control所做的第一件事,就是为该widget重新分配内存,并把模板的内容拷贝过来:
 static struct snd_soc_dapm_widget *
snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget)
{
struct snd_soc_dapm_widget *w;
int ret; if ((w = dapm_cnew_widget(widget)) == NULL)
return NULL;
由dapm_cnew_widget完成内存申请和拷贝模板的动作。接下来,根据widget的类型做不同的处理:
         switch (w->id) {
case snd_soc_dapm_regulator_supply:
w->regulator = devm_regulator_get(dapm->dev, w->name);
...... if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
ret = regulator_allow_bypass(w->regulator, true);
......
}
break;
case snd_soc_dapm_clock_supply:
#ifdef CONFIG_CLKDEV_LOOKUP
w->clk = devm_clk_get(dapm->dev, w->name);
......
#else
return NULL;
#endif
break;
default:
break;
}

对于snd_soc_dapm_regulator_supply类型的widget,根据widget的名称获取对应的regulator结构,对于snd_soc_dapm_clock_supply类型的widget,根据widget的名称,获取对应的clock结构。接下来,根据需要,在widget的名称前加入必要的前缀:

if (dapm->codec && dapm->codec->name_prefix)
w->name = kasprintf(GFP_KERNEL, "%s %s",
dapm->codec->name_prefix, widget->name);
else
w->name = kasprintf(GFP_KERNEL, "%s", widget->name);

然后,为不同类型的widget设置合适的power_check电源状态回调函数,widget类型和对应的power_check回调函数设置如下表所示:

widget的power_check回调函数
 
widget类型 power_check回调函数
mixer类:
snd_soc_dapm_switch
snd_soc_dapm_mixer
snd_soc_dapm_mixer_named_ctl
dapm_generic_check_power
mux类:
snd_soc_dapm_mux
snd_soc_dapm_mux
snd_soc_dapm_mux
dapm_generic_check_power
snd_soc_dapm_dai_out dapm_adc_check_power
snd_soc_dapm_dai_in dapm_dac_check_power
端点类:
snd_soc_dapm_adc
snd_soc_dapm_aif_out
snd_soc_dapm_dac
snd_soc_dapm_aif_in
snd_soc_dapm_pga
snd_soc_dapm_out_drv
snd_soc_dapm_input
snd_soc_dapm_output
snd_soc_dapm_micbias
snd_soc_dapm_spk
snd_soc_dapm_hp
snd_soc_dapm_mic
snd_soc_dapm_line
snd_soc_dapm_dai_link
dapm_generic_check_power
电源/时钟/影子widget:
snd_soc_dapm_supply
snd_soc_dapm_regulator_supply
snd_soc_dapm_clock_supply
snd_soc_dapm_kcontrol
dapm_supply_check_power
其它类型 dapm_always_on_check_power
 
 
 
当音频路径发生变化时,power_check回调会被调用,用于检查该widget的电源状态是否需要更新。power_check设置完成后,需要设置widget所属的codec、platform和context,几个用于音频路径的链表也需要初始化,然后,把该widget加入到声卡的widgets链表中:
         w->dapm = dapm;
w->codec = dapm->codec;
w->platform = dapm->platform;
INIT_LIST_HEAD(&w->sources);
INIT_LIST_HEAD(&w->sinks);
INIT_LIST_HEAD(&w->list);
INIT_LIST_HEAD(&w->dirty);
list_add(&w->list, &dapm->card->widgets);
几个链表的作用如下:
  • sources    用于链接所有连接到该widget输入端的snd_soc_path结构
  • sinks    用于链接所有连接到该widget输出端的snd_soc_path结构
  • list    用于链接到声卡的widgets链表
  • dirty    用于链接到声卡的dapm_dirty链表

最后,把widget设置为connect状态:

         /* machine layer set ups unconnected pins and insertions */
w->connected = ;
return w;
}

connected字段代表着引脚的连接状态,目前,只有以下这些widget使用connected字段:

  • snd_soc_dapm_output
  • snd_soc_dapm_input
  • snd_soc_dapm_hp
  • snd_soc_dapm_spk
  • snd_soc_dapm_line
  • snd_soc_dapm_vmid
  • snd_soc_dapm_mic
  • snd_soc_dapm_siggen
驱动程序可以使用以下这些api来设置引脚的连接状态:
  • snd_soc_dapm_enable_pin
  • snd_soc_dapm_force_enable_pin
  • snd_soc_dapm_disable_pin
  • snd_soc_dapm_nc_pin
到此,widget已经被正确地创建并初始化,而且被挂在声卡的widgets链表中,以后我们就可以通过声卡的widgets链表来遍历所有的widget,再次强调一下snd_soc_dapm_new_controls函数所完成的主要功能:
  • 为widget分配内存,并拷贝参数中传入的在驱动中定义好的模板
  • 设置power_check回调函数
  • 把widget挂在声卡的widgets链表中
 
 

二、为widget建立dapm kcontrol

定义一个widget,我们需要指定两个很重要的内容:一个是用于控制widget的电源状态的reg/shift等寄存器信息,另一个是用于控制音频路径切换的dapm kcontrol信息,这些dapm kcontrol有它们自己的reg/shift寄存器信息用于切换widget的路径连接方式。

 static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
......
/* card bind complete so register a sound card */
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, , &card->snd_card);
...... card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list); #ifdef CONFIG_DEBUG_FS
snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
#endif
......
if (card->dapm_widgets) /* 创建machine级别的widget */
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
card->num_dapm_widgets);
......
snd_soc_dapm_link_dai_widgets(card); /* 连接dai widget */ if (card->controls) /* 建立machine级别的普通kcontrol控件 */
snd_soc_add_card_controls(card, card->controls, card->num_controls); if (card->dapm_routes) /* 注册machine级别的路径连接信息 */
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);
...... if (card->fully_routed) /* 如果该标志被置位,自动把codec中没有路径连接信息的引脚设置为无用widget */
list_for_each_entry(codec, &card->codec_dev_list, card_list)
snd_soc_dapm_auto_nc_codec_pins(codec); snd_soc_dapm_new_widgets(card); /*初始化widget包含的dapm kcontrol、电源状态和连接状态*/ ret = snd_card_register(card->snd_card);
......
card->instantiated = ;
snd_soc_dapm_sync(&card->dapm);
......
return ;
}

正如我添加的注释中所示,在完成machine级别的widget和route处理之后,调用的snd_soc_dapm_new_widgets函数,来为所有已经注册的widget初始化他们所包含的dapm kcontrol,并初始化widget的电源状态和路径连接状态。下面我们看看snd_soc_dapm_new_widgets函数的工作过程。

2.1 snd_soc_dapm_new_widgets函数:

 int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
......
list_for_each_entry(w, &card->widgets, list)
{
if (w->new)
continue; if (w->num_kcontrols) {
w->kcontrols = kzalloc(w->num_kcontrols *
sizeof(struct snd_kcontrol *),
GFP_KERNEL);
......
}

接着,对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol:

                 switch(w->id) {
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
dapm_new_mixer(w);
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_virt_mux:
case snd_soc_dapm_value_mux:
dapm_new_mux(w);
break;
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
dapm_new_pga(w);
break;
default:
break;
}

需要用到的创建函数分别是:

  • dapm_new_mixer()    对于mixer类型,用该函数创建dapm kcontrol;
  • dapm_new_mux()   对于mux类型,用该函数创建dapm kcontrol;
  • dapm_new_pga()   对于pga类型,用该函数创建dapm kcontrol;
 接着,设置new字段,表明该widget已经初始化完成,我们还要把该widget加入到声卡的dapm_dirty链表中,表明该widget的状态发生了变化,稍后在合适的时刻,dapm框架会扫描dapm_dirty链表,统一处理所有已经变化的widget。为什么要统一处理?因为dapm要控制各种widget的上下电顺序,同时也是为了减少寄存器的读写次数(多个widget可能使用同一个寄存器):
                 w->new = ;

                 dapm_mark_dirty(w, "new widget");
dapm_debugfs_add_widget(w);
}

最后,通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上的widget的状态改变:

 dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
......
return ;
 

三、为widget建立连接关系

如果widget之间没有连接关系,dapm就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,dapm可以顺着这条路径,统一控制路径上所有widget的电源状态,前面我们已经知道,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终,驱动程序使用api函数snd_soc_dapm_add_routes来注册这些连接信息,接下来我们就是要分析该函数的具体实现方式:

 int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route, int num)
{
int i, r, ret = ; mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = ; i < num; i++) {
r = snd_soc_dapm_add_route(dapm, route);
......
route++;
}
mutex_unlock(&dapm->card->dapm_mutex); return ret;
}

该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我们进入snd_soc_dapm_add_route函数看看:

 static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route)
{
struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
const char *sink;
const char *source;
......
list_for_each_entry(w, &dapm->card->widgets, list) {
if (!wsink && !(strcmp(w->name, sink))) {
wtsink = w;
if (w->dapm == dapm)
wsink = w;
continue;
}
if (!wsource && !(strcmp(w->name, source))) {
wtsource = w;
if (w->dapm == dapm)
wsource = w;
}
}

上面的代码我再次省略了关于名称前缀的处理部分。我们可以看到,用widget的名字来比较,遍历声卡的widgets链表,找出源widget和目的widget的指针,这段代码虽然正确,但我总感觉少了一个判断退出循环的条件,如果链表的开头就找到了两个widget,还是要遍历整个链表才结束循环,好浪费时间。

下面,如果在本dapm context中没有找到,则使用别的dapm context中找到的widget:

         if (!wsink)
wsink = wtsink;
if (!wsource)
wsource = wtsource;

最后,使用来增加一条连接信息:

         ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
route->connected);
...... return ;
}
snd_soc_dapm_add_path函数是整个调用链条中的关键,我们来分析一下:
 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
const char *control,
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink))
{
struct snd_soc_dapm_path *path;
int ret; path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
if (!path)
return -ENOMEM; path->source = wsource;
path->sink = wsink;
path->connected = connected;
INIT_LIST_HEAD(&path->list);
INIT_LIST_HEAD(&path->list_kcontrol);
INIT_LIST_HEAD(&path->list_source);
INIT_LIST_HEAD(&path->list_sink);

最后,使用来增加一条连接信息:

 ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
route->connected);
...... return ;
}

snd_soc_dapm_add_path函数是整个调用链条中的关键,我们来分析一下:(注意linux3.10.28代码没有相应的snd_soc_dapm_add_path函数,在linux3.12才有设计snd_soc_dapm_add_path函数)

 static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
const char *control,
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink))
{
struct snd_soc_dapm_path *path;
int ret; path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
if (!path)
return -ENOMEM; path->source = wsource;
path->sink = wsink;
path->connected = connected;
INIT_LIST_HEAD(&path->list);
INIT_LIST_HEAD(&path->list_kcontrol);
INIT_LIST_HEAD(&path->list_source);
INIT_LIST_HEAD(&path->list_sink);
 函数的一开始,首先为这个连接分配了一个snd_soc_path结构,path的source和sink字段分别指向源widget和目的widget,connected字段保存connected回调函数,初始化几个snd_soc_path结构中的几个链表。
 /* check for external widgets */
if (wsink->id == snd_soc_dapm_input) {
if (wsource->id == snd_soc_dapm_micbias ||
wsource->id == snd_soc_dapm_mic ||
wsource->id == snd_soc_dapm_line ||
wsource->id == snd_soc_dapm_output)
wsink->ext = ;
}
if (wsource->id == snd_soc_dapm_output) {
if (wsink->id == snd_soc_dapm_spk ||
wsink->id == snd_soc_dapm_hp ||
wsink->id == snd_soc_dapm_line ||
wsink->id == snd_soc_dapm_input)
wsource->ext = ;
}

这段代码用于判断是否有外部连接关系,如果有,置位widget的ext字段。判断方法从代码中可以方便地看出:

  • 目的widget是一个输入脚,如果源widget是mic、line、micbias或output,则认为目的widget具有外部连接关系。
  • 源widget是一个输出脚,如果目的widget是spk、hp、line或input,则认为源widget具有外部连接关系。
 dapm_mark_dirty(wsource, "Route added");
dapm_mark_dirty(wsink, "Route added"); /* connect static paths */
if (control == NULL) {
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = ;
return ;
}

因为增加了连结关系,所以把源widget和目的widget加入到dapm_dirty链表中。如果没有kcontrol来控制该连接关系,则这是一个静态连接,直接用path把它们连接在一起。在接着往下看:

         /* connect dynamic paths */
switch (wsink->id) {
case snd_soc_dapm_adc:
case snd_soc_dapm_dac:
case snd_soc_dapm_pga:
case snd_soc_dapm_out_drv:
case snd_soc_dapm_input:
case snd_soc_dapm_output:
case snd_soc_dapm_siggen:
case snd_soc_dapm_micbias:
case snd_soc_dapm_vmid:
case snd_soc_dapm_pre:
case snd_soc_dapm_post:
case snd_soc_dapm_supply:
case snd_soc_dapm_regulator_supply:
case snd_soc_dapm_clock_supply:
case snd_soc_dapm_aif_in:
case snd_soc_dapm_aif_out:
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
case snd_soc_dapm_dai_link:
case snd_soc_dapm_kcontrol:
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_sink, &wsink->sources);
list_add(&path->list_source, &wsource->sinks);
path->connect = ;
return ;

按照目的widget来判断,如果属于以上这些类型,直接把它们连接在一起即可,这段感觉有点多余,因为通常以上这些类型的widget本来也没有kcontrol,直接用上一段代码就可以了,也许是dapm的作者们想着以后可能会有所扩展吧。

 case snd_soc_dapm_mux:
case snd_soc_dapm_virt_mux:
case snd_soc_dapm_value_mux:
ret = dapm_connect_mux(dapm, wsource, wsink, path, control,
&wsink->kcontrol_news[]);
if (ret != )
goto err;
break;
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
ret = dapm_connect_mixer(dapm, wsource, wsink, path, control);
if (ret != )
goto err;
break;

当widget之间通过path进行连接之后,他们之间的关系就如下图所示:

ALSA声卡驱动的DAPM(二)-建立过程

到这里为止,我们为声卡创建并初始化好了所需的widget,各个widget也通过path连接在了一起,接下来,dapm等待用户的指令,一旦某个dapm kcontrol被用户空间改变,利用这些连接关系,dapm会重新创建音频路径,脱离音频路径的widget会被下电,加入音频路径的widget会被上电,所有的上下电动作都会自动完成,用户空间的应用程序无需关注这些变化,它只管按需要改变某个dapm kcontrol即可。
 

ALSA声卡驱动的DAPM(二)-建立过程的更多相关文章

  1. ALSA声卡驱动的DAPM(一)-DPAM详解

    最近使用tinymix 调试相应的音频通道,但是一直不知道音频通道的原理是什么.所以百度了一下,百度结果是与DPAM有关. 一.DAPM简介: DAPM是Dynamic Audio Power Man ...

  2. ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系

    前面我们主要着重于codec.platform.machine驱动程序中如何使用和建立dapm所需要的widget,route,这些是音频驱动开发人员必须要了解的内容,经过前几章的介绍,我们应该知道如 ...

  3. ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)

    前面的六篇文章,我们已经讨论了dapm关于动态电源管理的有关知识,包括widget的创建和初始化,widget之间的连接以及widget的上下电顺序等等.本章我们准备讨论dapm框架中的另一个机制:事 ...

  4. ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身

    设计dapm的主要目的之一,就是希望声卡上的各种部件的电源按需分配,需要的就上电,不需要的就下电,使得整个音频系统总是处于最小的耗电状态,最主要的就是,这一切对用户空间的应用程序是透明的,也就是说,用 ...

  5. ALSA声卡驱动中的DAPM详解之三:如何定义各种widget

    上一节中,介绍了DAPM框架中几个重要的数据结构:snd_soc_dapm_widget,snd_soc_dapm_path,snd_soc_dapm_route.其中snd_soc_dapm_pat ...

  6. ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

    前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path.之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开 ...

  7. ALSA声卡驱动中的DAPM详解之一:kcontrol

    DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态 ...

  8. Linux ALSA声卡驱动之八:ASoC架构中的Platform

    1.  Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...

  9. 36、ALSA声卡驱动和应用

    (注意:内核上电的时候会把一些没运行的控制器模块的时钟都关掉,所有在写驱动的时候需要在使用的使用使用clk_get和clk_enable使能时钟) (说明:与ALSA声卡对应的是OSS架构,第二期视频 ...

随机推荐

  1. HTC学习笔记

    添加一个属性的setter, getter 建立一个页面 <html> <head> <title>TODO supply a title</title&gt ...

  2. 高版本正方教务系统上传后缀过滤不严导致能直接上传Webshell

    在旧版本中有一个利用插件上传文件的漏洞,但是在新版本中已经没有了这个插件.这个漏洞是由于过滤不严造成的,可以直接上传Webshell进行提权,由于代码在DLL中,全国大部分高校均有此漏洞,影响范围很大 ...

  3. Centos6快速yum lamp

    yum install httpd httpd-devel mysql mysql-server mysql-devel  php php-mysql php-common php-gd php-mb ...

  4. 《University Calculus》-chape4-导数的应用-极值点的二阶导数检验法

    函数凹凸性检验: 很容易看到,观察类似抛物线这类曲线,能够看到它们有一个向上凹或者向下凹的这样一个过程,而我们将这个过程细化并观察一系列点的导数的变化情况我们给出如下的定义: (1)如果函数图像在区间 ...

  5. NSIS脚本根据操作系统版本动态决定默认安装目录

    问题描述: 因为windows XP和windows 7的program files不同(有program files(x86)),所以需要动态根据系统的位数设置默认安装目录 References: ...

  6. 事件委托小demo&lpar;原生版&rpar;

    <style type="text/css"> body, div, span { margin:; padding:; font-family: "\5FA ...

  7. PAT &lpar;Basic Level&rpar; Practise (中文)-1021&period; 个位数统计 &lpar;15&rpar;

    1021. 个位数统计 (15) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 给定一个k位整数N = dk-1 ...

  8. Hadoop 2&period;7&period;4 HDFS&plus;YRAN HA删除datanode和nodemanager

    当前集群 主机名称 IP地址 角色 统一安装目录 统一安装用户 sht-sgmhadoopnn-01 172.16.101.55 namenode,resourcemanager /usr/local ...

  9. socket架构

    套接字基本概念 Socket是应用层与Tcp/ip协议族通信的中间软件抽象层,它是一组接口.在设计模式中,socket其实就是一个门面模式,它把复杂的tcp/ip协议族隐藏在socket接口后面,对用 ...

  10. Mysql 的远程设置 和链接方式

    Mysql 设置远程链接: > grant all privileges on *.* to 用户@localhost identified by '密码'; > flush privil ...