前面几章分析了Codec、Platform、Machine驱动的组成部分及其注册过程,这三者都是物理设备相关的,大家应该对音频物理链路有了一定的认知。接着分析音频驱动的中间层,由于这些并不是真正的物理设备,故我们称之为逻辑设备。

PCM逻辑设备,我们又习惯称之为PCM中间层或pcm native,起着承上启下的作用:往上是与用户态接口的交互,实现音频数据在用户态和内核态之间的拷贝;往下是触发codec、platform、machine的操作函数,实现音频数据在dma_buffer<-> cpu_dai <-> codec之间的传输。

后面章节将会对这个过程详细分析,这里还是先从声卡的注册谈起。

声卡驱动中,一般挂载着多个逻辑设备,看看我们计算机的声卡驱动有几个逻辑设备:

[plain] view plaincopy print?
  1. $ cat /proc/asound/devices
  2. 1:        : sequencer
  3. 2: [ 0- 7]: digital audio playback
  4. 3: [ 0- 3]: digital audio playback
  5. 4: [ 0- 2]: digital audio capture
  6. 5: [ 0- 0]: digital audio playback
  7. 6: [ 0- 0]: digital audio capture
  8. 7: [ 0- 3]: hardware dependent
  9. 8: [ 0- 0]: hardware dependent
  10. 9: [ 0]   : control
  11. 33:        : timer

·          digital audio playback:用于回放的PCM设备

·          digital audio capture:用于录制的PCM设备

·          control:用于声卡控制的CTL设备,如通路控制、音量调整等

·          timer:定时器设备

·          sequencer:音序器设备

嵌入式系统中,通常我们更关心PCM和CTL这两种设备。

设备节点如下:

[plain] view plaincopy print?
  1. $ ll /dev/snd
  2. drwxr-xr-x   3 root root      260 Feb 26 13:59 ./
  3. drwxr-xr-x  16 root root     4300 Mar  6 17:07 ../
  4. drwxr-xr-x   2 root root       60 Feb 26 13:59 by-path/
  5. crw-rw---T+  1 root audio 116,  9 Feb 26 13:59 controlC0
  6. crw-rw---T+  1 root audio 116,  8 Feb 26 13:59 hwC0D0
  7. crw-rw---T+  1 root audio 116,  7 Feb 26 13:59 hwC0D3
  8. crw-rw---T+  1 root audio 116,  6 Feb 26 13:59 pcmC0D0c
  9. crw-rw---T+  1 root audio 116,  5 Mar  6 19:08 pcmC0D0p
  10. crw-rw---T+  1 root audio 116,  4 Feb 26 13:59 pcmC0D2c
  11. crw-rw---T+  1 root audio 116,  3 Feb 26 13:59 pcmC0D3p
  12. crw-rw---T+  1 root audio 116,  2 Feb 26 13:59 pcmC0D7p
  13. crw-rw---T+  1 root audio 116,  1 Feb 26 13:59 seq
  14. crw-rw---T+  1 root audio 116, 33 Feb 26 13:59 timer

可以看到这些设备节点的Major=116,Minor则与/proc/asound/devices所列的对应起来,都是字符设备。上层可以通过open/close/read/write/ioctl等系统调用来操作声卡设备,和其他字符设备类似,但一般情况下我们使用已封装好的用户接口库如tinyalsa、alsa-lib。

6.1. 声卡结构概述

回顾下ASoC是如何注册声卡的,详细请参考章节5.ASoC machine driver,这里仅简单陈述下:

·          Machine驱动初始化时,name="soc-audio"的platform_device与platform_driver匹配成功,触发soc_probe()调用;

·          继而调用snd_soc_register_card(),该函数做的事情很多:

1.  为每个音频物理链路找到对应的codec、codec_dai、cpu_dai、platform设备实例,完成dai_link的绑定;

2.  调用snd_card_create()创建声卡;

3.  依次回调cpu_dai、codec、platform的probe()函数,完成物理设备的初始化;

·          随后调用soc_new_pcm()创建pcm逻辑设备:

1.  设置pcm native中要使用的pcm操作函数,这些函数用于操作音频物理设备,包括machine、codec_dai、cpu_dai、platform;

2.  调用snd_pcm_new()创建pcm设备,回放子流实例和录制子流实例都在这里创建;

3.  回调platform驱动的pcm_new(),完成音频dma设备初始化和dma buffer内存分配;

·          最后调用snd_card_register()注册声卡。

关于音频物理设备部分(Codec/Platform/Machine)不再累述,下面详细分析声卡和PCM逻辑设备的注册过程。

上面提到声卡驱动上挂着多个逻辑子设备,有pcm(音频数据流)、control(混音器控制)、midi(迷笛)、timer(定时器)、sequencer(音序器)等。

[plain] view plaincopy print?
  1. +-----------+
  2. | snd_card  |
  3. +-----------+
  4. |   |   |
  5. +-----------+   |   +------------+
  6. |               |                |
  7. +-----------+    +-----------+    +-----------+
  8. |  snd_pcm  |    |snd_control|    | snd_timer |    ...
  9. +-----------+    +-----------+    +-----------+

这些与声音相关的逻辑设备都在结构体snd_card管理之下,可以说snd_card是alsa中最顶层的结构。

我们再看看alsa声卡驱动的大致结构图(不是严格的UML类图,有结构体定义、模块关系、函数调用,方便标示结构模块的层次及关系):

snd_cards:记录着所注册的声卡实例,每个声卡实例有着各自的逻辑设备,如PCM设备、CTL设备、MIDI设备等,并一一记录到snd_card的devices链表上。

snd_minors:记录着所有逻辑设备的上下文信息,它是声卡逻辑设备与系统调用API之间的桥梁;每个snd_minor在逻辑设备注册时被填充,在逻辑设备使用时就可以从该结构体中得到相应的信息(主要是系统调用函数集file_operations)。

6.2. 声卡的创建

声卡实例通过函数snd_card_create()来创建,其函数原型:

[cpp] view plaincopy print?
  1. /**
  2. *  snd_card_create - create and initialize a soundcard structure
  3. *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
  4. *  @xid: card identification (ASCII string)
  5. *  @module: top level module for locking
  6. *  @extra_size: allocate this extra size after the main soundcard structure
  7. *  @card_ret: the pointer to store the created card instance
  8. *
  9. *  Creates and initializes a soundcard structure.
  10. *
  11. *  The function allocates snd_card instance via kzalloc with the given
  12. *  space for the driver to use freely.  The allocated struct is stored
  13. *  in the given card_ret pointer.
  14. *
  15. *  Returns zero if successful or a negative error code.
  16. */
  17. int snd_card_create(int idx, const char *xid,
  18. struct module *module, int extra_size,
  19. struct snd_card **card_ret)

注释非常详细,简单说下:

·          idx:声卡的编号,如为-1,则由系统自动分配

·          xid:声卡标识符,如为NULL,则以snd_card的shortname或longname代替

·          card_ret:返回所创建的声卡实例的指针

如下是我的计算机的声卡信息:

[plain] view plaincopy print?
  1. $ cat /proc/asound/cards
  2. 0 [PCH            ]: HDA-Intel - HDA Intel PCH
  3. HDA Intel PCH at 0xf7c30000 irq 47

编号number:0

标识符id:PCH

shortname:HDAIntel PCH

longname:HDAIntel PCH at 0xf7c30000 irq 47

shortname、longname常用于打印信息,上面的声卡信息是通过如下函数打印出来的:

[cpp] view plaincopy print?
  1. static void snd_card_info_read(struct snd_info_entry *entry,
  2. struct snd_info_buffer *buffer)
  3. {
  4. int idx, count;
  5. struct snd_card *card;
  6. for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
  7. mutex_lock(&snd_card_mutex);
  8. if ((card = snd_cards[idx]) != NULL) {
  9. count++;
  10. snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",
  11. idx,
  12. card->id,
  13. card->driver,
  14. card->shortname);
  15. snd_iprintf(buffer, "                      %s\n",
  16. card->longname);
  17. }
  18. mutex_unlock(&snd_card_mutex);
  19. }
  20. if (!count)
  21. snd_iprintf(buffer, "--- no soundcards ---\n");
  22. }

6.3. 逻辑设备的创建

当声卡实例建立后,接着可以创建声卡下面的各个逻辑设备了。每个逻辑设备创建时,都会调用snd_device_new()生成一个snd_device实例,并把该实例挂到声卡snd_card的devices链表上。

alsa驱动为各种逻辑设备提供了创建接口,如下:

PCM

snd_pcm_new()

CONTROL

snd_ctl_create()

MIDI

snd_rawmidi_new()

TIMER

snd_timer_new()

SEQUENCER

snd_seq_device_new()

JACK

snd_jack_new()

这些接口的一般过程如下:

[cpp] view plaincopy print?
  1. int snd_xxx_new()
  2. {
  3. // 这些接口供逻辑设备注册时回调
  4. static struct snd_device_ops ops = {
  5. .dev_free = snd_xxx_dev_free,
  6. .dev_register = snd_xxx_dev_register,
  7. .dev_disconnect = snd_xxx_dev_disconnect,
  8. };
  9. // 逻辑设备实例初始化
  10. // 新建一个设备实例snd_device,挂到snd_card的devices链表上,把该逻辑设备纳入声卡的管理当中
  11. return snd_device_new(card, SNDRV_DEV_xxx, card, &ops);
  12. }

其中snd_device_ops是声卡逻辑设备的注册相关函数集,dev_register()回调尤其重要,它在声卡注册时被调用,用于建立系统的设备文件节点,/dev/snd/目录的设备文件节点都是在这里创建的。

例如snd_ctl_dev_register():

[cpp] view plaincopy print?
  1. // CTL设备的系统调用接口
  2. static const struct file_operations snd_ctl_f_ops =
  3. {
  4. .owner =    THIS_MODULE,
  5. .read =     snd_ctl_read,
  6. .open =     snd_ctl_open,
  7. .release =  snd_ctl_release,
  8. .llseek =   no_llseek,
  9. .poll =     snd_ctl_poll,
  10. .unlocked_ioctl =   snd_ctl_ioctl,
  11. .compat_ioctl = snd_ctl_ioctl_compat,
  12. .fasync =   snd_ctl_fasync,
  13. };
  14. /*
  15. * registration of the control device
  16. */
  17. static int snd_ctl_dev_register(struct snd_device *device)
  18. {
  19. struct snd_card *card = device->device_data;
  20. int err, cardnum;
  21. char name[16];
  22. if (snd_BUG_ON(!card))
  23. return -ENXIO;
  24. cardnum = card->number;
  25. if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
  26. return -ENXIO;
  27. sprintf(name, "controlC%i", cardnum);
  28. if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
  29. &snd_ctl_f_ops, card, name)) < 0)
  30. return err;
  31. return 0;
  32. }

事实是调用snd_register_device_for_dev():

·          分配并初始化一个snd_minor实例;

·          保存该snd_minor实例到snd_minors数组中;

·          调用device_create()生成设备文件节点。

[cpp] view plaincopy print?
  1. /**
  2. * snd_register_device_for_dev - Register the ALSA device file for the card
  3. * @type: the device type, SNDRV_DEVICE_TYPE_XXX
  4. * @card: the card instance
  5. * @dev: the device index
  6. * @f_ops: the file operations
  7. * @private_data: user pointer for f_ops->open()
  8. * @name: the device file name
  9. * @device: the &struct device to link this new device to
  10. *
  11. * Registers an ALSA device file for the given card.
  12. * The operators have to be set in reg parameter.
  13. *
  14. * Returns zero if successful, or a negative error code on failure.
  15. */
  16. int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
  17. const struct file_operations *f_ops,
  18. void *private_data,
  19. const char *name, struct device *device)
  20. {
  21. int minor;
  22. struct snd_minor *preg;
  23. if (snd_BUG_ON(!name))
  24. return -EINVAL;
  25. preg = kmalloc(sizeof *preg, GFP_KERNEL);
  26. if (preg == NULL)
  27. return -ENOMEM;
  28. preg->type = type;
  29. preg->card = card ? card->number : -1;
  30. preg->device = dev;
  31. preg->f_ops = f_ops;
  32. preg->private_data = private_data;
  33. mutex_lock(&sound_mutex);
  34. #ifdef CONFIG_SND_DYNAMIC_MINORS
  35. minor = snd_find_free_minor(type);
  36. #else
  37. minor = snd_kernel_minor(type, card, dev);
  38. if (minor >= 0 && snd_minors[minor])
  39. minor = -EBUSY;
  40. #endif
  41. if (minor < 0) {
  42. mutex_unlock(&sound_mutex);
  43. kfree(preg);
  44. return minor;
  45. }
  46. snd_minors[minor] = preg;
  47. preg->dev = device_create(sound_class, device, MKDEV(major, minor),
  48. private_data, "%s", name);
  49. if (IS_ERR(preg->dev)) {
  50. snd_minors[minor] = NULL;
  51. mutex_unlock(&sound_mutex);
  52. minor = PTR_ERR(preg->dev);
  53. kfree(preg);
  54. return minor;
  55. }
  56. mutex_unlock(&sound_mutex);
  57. return 0;
  58. }

上面过程是声卡注册时才被回调的。

6.4. 声卡的注册

当声卡下的所有逻辑设备都已经准备就绪后,就可以调用snd_card_register()注册声卡了:

·          创建声卡的sysfs设备;

·          调用snd_device_register_all()注册所有挂在该声卡下的逻辑设备;

·          建立proc信息文件和sysfs属性文件。

[cpp] view plaincopy print?
  1. /**
  2. *  snd_card_register - register the soundcard
  3. *  @card: soundcard structure
  4. *
  5. *  This function registers all the devices assigned to the soundcard.
  6. *  Until calling this, the ALSA control interface is blocked from the
  7. *  external accesses.  Thus, you should call this function at the end
  8. *  of the initialization of the card.
  9. *
  10. *  Returns zero otherwise a negative error code if the registration failed.
  11. */
  12. int snd_card_register(struct snd_card *card)
  13. {
  14. int err;
  15. if (snd_BUG_ON(!card))
  16. return -EINVAL;
  17. // 创建sysfs设备,声卡的class将会出现在/sys/class/sound/下面
  18. if (!card->card_dev) {
  19. card->card_dev = device_create(sound_class, card->dev,
  20. MKDEV(0, 0), card,
  21. "card%i", card->number);
  22. if (IS_ERR(card->card_dev))
  23. card->card_dev = NULL;
  24. }
  25. // 遍历挂在该声卡的所有逻辑设备,回调各snd_device的ops->dev_register()完成各逻辑设备的注册
  26. if ((err = snd_device_register_all(card)) < 0)
  27. return err;
  28. mutex_lock(&snd_card_mutex);
  29. if (snd_cards[card->number]) {
  30. /* already registered */
  31. mutex_unlock(&snd_card_mutex);
  32. return 0;
  33. }
  34. if (*card->id) {
  35. /* make a unique id name from the given string */
  36. char tmpid[sizeof(card->id)];
  37. memcpy(tmpid, card->id, sizeof(card->id));
  38. snd_card_set_id_no_lock(card, tmpid, tmpid);
  39. } else {
  40. /* create an id from either shortname or longname */
  41. const char *src;
  42. src = *card->shortname ? card->shortname : card->longname;
  43. snd_card_set_id_no_lock(card, src,
  44. retrieve_id_from_card_name(src));
  45. }
  46. snd_cards[card->number] = card; // 把该声卡实例保存到snd_cards数组中
  47. mutex_unlock(&snd_card_mutex);
  48. // 声卡相关信息,见:/proc/asound/card0
  49. init_info_for_card(card);
  50. #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
  51. if (snd_mixer_oss_notify_callback)
  52. snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
  53. #endif
  54. // 声卡的sysfs属性节点
  55. if (card->card_dev) {
  56. err = device_create_file(card->card_dev, &card_id_attrs);
  57. if (err < 0)
  58. return err;
  59. err = device_create_file(card->card_dev, &card_number_attrs);
  60. if (err < 0)
  61. return err;
  62. }
  63. return 0;
  64. }

至此完成了声卡及声卡下的所有逻辑设备的注册,用户态应用可以通过系统调用来访问这些设备了。

6.5. PCM设备的创建

最后我们简单说说PCM设备的建立过程:

snd_pcm_set_ops:设置PCM设备的操作接口,设置完成后,PCM设备就可以控制/操作底层音频物理设备了。

snd_pcm_new

·          创建一个PCM设备实例snd_pcm;

·          创建playback stream和capture stream,旗下的substream也同时建立;

·          调用snd_device_new()把PCM设备挂到声卡的devices链表上。

[cpp] view plaincopy print?
  1. static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
  2. int playback_count, int capture_count, bool internal,
  3. struct snd_pcm **rpcm)
  4. {
  5. struct snd_pcm *pcm;
  6. int err;
  7. static struct snd_device_ops ops = {
  8. .dev_free = snd_pcm_dev_free,
  9. .dev_register = snd_pcm_dev_register,
  10. .dev_disconnect = snd_pcm_dev_disconnect,
  11. };
  12. if (snd_BUG_ON(!card))
  13. return -ENXIO;
  14. if (rpcm)
  15. *rpcm = NULL;
  16. pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
  17. if (pcm == NULL) {
  18. snd_printk(KERN_ERR "Cannot allocate PCM\n");
  19. return -ENOMEM;
  20. }
  21. pcm->card = card;
  22. pcm->device = device;
  23. pcm->internal = internal;
  24. if (id)
  25. strlcpy(pcm->id, id, sizeof(pcm->id));
  26. if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
  27. snd_pcm_free(pcm);
  28. return err;
  29. }
  30. if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
  31. snd_pcm_free(pcm);
  32. return err;
  33. }
  34. mutex_init(&pcm->open_mutex);
  35. init_waitqueue_head(&pcm->open_wait);
  36. if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
  37. snd_pcm_free(pcm);
  38. return err;
  39. }
  40. if (rpcm)
  41. *rpcm = pcm;
  42. return 0;
  43. }

我们再看看PCM设备的系统调用接口:

[cpp] view plaincopy print?
  1. const struct file_operations snd_pcm_f_ops[2] = {
  2. {
  3. .owner =        THIS_MODULE,
  4. .write =        snd_pcm_write,
  5. .aio_write =        snd_pcm_aio_write,
  6. .open =         snd_pcm_playback_open,
  7. .release =      snd_pcm_release,
  8. .llseek =       no_llseek,
  9. .poll =         snd_pcm_playback_poll,
  10. .unlocked_ioctl =   snd_pcm_playback_ioctl,
  11. .compat_ioctl =     snd_pcm_ioctl_compat,
  12. .mmap =         snd_pcm_mmap,
  13. .fasync =       snd_pcm_fasync,
  14. .get_unmapped_area =    snd_pcm_get_unmapped_area,
  15. },
  16. {
  17. .owner =        THIS_MODULE,
  18. .read =         snd_pcm_read,
  19. .aio_read =     snd_pcm_aio_read,
  20. .open =         snd_pcm_capture_open,
  21. .release =      snd_pcm_release,
  22. .llseek =       no_llseek,
  23. .poll =         snd_pcm_capture_poll,
  24. .unlocked_ioctl =   snd_pcm_capture_ioctl,
  25. .compat_ioctl =     snd_pcm_ioctl_compat,
  26. .mmap =         snd_pcm_mmap,
  27. .fasync =       snd_pcm_fasync,
  28. .get_unmapped_area =    snd_pcm_get_unmapped_area,
  29. }
  30. };

snd_pcm_f_ops作为snd_register_device_for_dev()的参数被传入,并被记录在snd_minors[minor]中的字段f_ops中。snd_pcm_f_ops[0]是回放使用的系统调用接口,snd_pcm_f_ops[1]是录制使用的系统调用接口。

6.6. 参考资料

·          Linux ALSA声卡驱动之二:声卡的创建:http://blog.csdn.net/droidphone/article/details/6289712

·          Linux ALSA声卡驱动之三:PCM设备的创建:http://blog.csdn.net/droidphone/article/details/6308006

转载于:https://www.cnblogs.com/jamboo/articles/5803483.html

PCM data flow - 6 - 声卡和PCM设备的建立过程相关推荐

  1. PCM data flow - part 4: ASoC platform driver

    http://blog.csdn.net/azloong/article/details/17317829 概述中提到音频Platform驱动主要作用是音频数据的传输,这里又细分为两步: ·     ...

  2. PCM data flow - 3 - ASoC codec driver

    上一章提到codec_drv的几个组成部分,下面逐一介绍,基本是以内核文档Documentation/sound/alsa/soc/codec.txt中的内容为脉络来分析的.codec的作用,在概述中 ...

  3. PCM data flow - 2 - ASoC data structure

    ASoC:ALSA System on Chip,是建立在标准ALSA驱动之上,为了更好支持嵌入式系统和移动设备中的音频codec的一套软件体系,它依赖于标准ALSA驱动框架.内核文档Document ...

  4. PCM data flow - 1 - Overview

    Kernel     - 3.4.5 SoC        - Samsung exynos CODEC      - WM8994 Machine    - goni_wm8994 Userspac ...

  5. Spring Cloud Data Flow整合UAA之使用LDAP进行账号管理

    我最新最全的文章都在 南瓜慢说 www.pkslow.com ,欢迎大家来喝茶! 1 前言 Spring Cloud Data Flow整合UAA的文章已经写了两篇,之前的方案是把用户信息保存在数据库 ...

  6. Spring Cloud Data Flow整合UAA使用外置数据库和API接口

    我最新最全的文章都在 南瓜慢说 www.pkslow.com ,欢迎大家来喝茶! 1 前言 之前的文章<Spring Cloud Data Flow整合Cloudfoundry UAA服务做权限 ...

  7. Spring Cloud Data Flow整合Cloudfoundry UAA服务做权限控制

    我最新最全的文章都在 南瓜慢说 www.pkslow.com ,欢迎大家来喝茶! 1 前言 关于Spring Cloud Data Flow这里不多介绍,有兴趣可以看下面的文章.本文主要介绍如何整合D ...

  8. Audio System 四 之声卡和PCM设备建立过程

    Audio System 四 之声卡和PCM设备建立过程 九.声卡和PCM设备建立过程 9.1 声卡设备 9.1.1 声卡结构概述 9.1.2 声卡的创建snd_card_create() 9.1.3 ...

  9. android中调用fft函数,J使用PCM数据在Android中转换FFT(JTransforms FFT in Android from PCM data)...

    J使用PCM数据在Android中转换FFT(JTransforms FFT in Android from PCM data) 我一直在玩这个游戏已经有一段时间了,我无法弄清楚我在这里要做的事情. ...

最新文章

  1. Hadoop与Alpach Spark的区别
  2. HADOOP2单机版
  3. sdk版本过低怎么办_滴滴ElasticSearch平台跨版本升级以及平台重构之路
  4. 给 C# 开发者的代码审查清单
  5. VC/MFC如何设置对话框背景颜色
  6. CCF 201409-1 相邻数对
  7. 今天开始复习toefl,mark一下
  8. Java笔记-Log4j在Spring Boot中的使用
  9. GO、Rust 这些新一代高并发编程语言为何都极其讨厌共享内存?
  10. [python] 为 pip 更换国内源
  11. 时间序列信号处理(一)-----变分模态分解(VMD)
  12. 17.1.1 颜色和 RGBA 值
  13. 111-unsplash-for-chrome-2021-07-22
  14. 什么是混合APP开发
  15. 目标检测00-05:mmdetection(Foveabox为例)-白话给你讲论文-翻译无死角-1
  16. 【C语言典例】——day4:加油站加油【Switch】
  17. WinRAR的命令行模式与用法介绍
  18. bzoj3698: XWW的难题 有上下界的网络流
  19. Linux设备模型(3)_Uevent
  20. 渝北统景碑口规划开发_统景风景区旅游镇总体规划(2011—2030)说明书

热门文章

  1. mips语言实现 f(n) = f(n-1) + 2*f(n-2) + 3*f(n-3)
  2. MSPlus DatePicker WebControl FreeVersion 1.1.0906 发布啦!
  3. 修改placeorder html,数字分发Web服务DDWSPlaceOrder-服务手册-Partner.PDF
  4. 中油C语言第一次在线作业,中石油华东《程序设计(C语言)》2020年春季学期在线作业(二)...
  5. python带我起飞_Python带我起飞:入门、进阶、商业实战
  6. python 爬取_使用 Python 和 BeautifulSoup 来做爬虫抓取
  7. join实例 oracle_oracle中join联合查询
  8. 5-11attention网络结构
  9. java js合并_JS合并单元格
  10. autocomplete触发事件_修改jQuery.autocomplete中遇到的键盘事件