Linux ALSA架構:PCM_OPEN流程 (二)

一? ?應用端

源碼路徑:? external\tinyalsa\pcm.c? ?external\tinyalsa\pcm_hw.c

struct pcm *pcm_open(unsigned int card, unsigned int device,unsigned int flags, struct pcm_config *config)
{...pcm->ops = &hw_ops;pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);/*實際是調用 pcm_hw.c 的pcm_hw_open 接口  open("/dev/snd/pcmC0D0c") 打開節點*/if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) {oops(&bad_pcm, errno, "cannot get info");goto fail_close;}if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {oops(&bad_pcm, errno, "cannot set hw params");goto fail_close;}/* get our refined hw_params */config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);pcm->buffer_size = config->period_count * config->period_size;if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {oops(&bad_pcm, errno, "cannot set sw params");goto fail;}
}

二?audio 節點 介紹

1. /dev/snd下的pcm設備節點介紹

我們 adb shell, 進入手機中,ls -al /dev/snd 看下,可以看到很多設備節點。
簡化如下:

$ cd /dev/snd 
$ ls –l 
crw-rw----+ 1 root audio 116, 8 2011-02-23 21:38 controlC0  ---> 用于聲卡的控制,例如通道選擇,混音,麥克風的控制等 
crw-rw----+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0 	---> 用于播放midi 音頻 
crw-rw----+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c 	---> 用于錄音的pcm 設備 1
crw-rw----+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p 	---> 用于播放的pcm 設備 1
crw-rw----+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p 	---> 用于播放的pcm 設備 2
crw-rw----+ 1 root audio 116, 3 2011-02-23 21:38 seq 		---> 音序器 
crw-rw----+ 1 root audio 116, 2 2011-02-23 21:38 timer 		---> 定時器 

其中,
C0D0 代表的是聲卡0 中的設備0,
pcmC0D0c 最后一個c 代表capture,
pcmC0D0p 最后一個p 代表 playback,
這些都是alsa-driver 中的命名規則。

2. /dev/snd下的pcm設備節點 創建過程分析

另外,還有一個發現,就,/dev/snd 下面所有的節點的主設備號 都是 116 ,面次設備號各不相同。

原因是因為,
在alsa 中所有的節點都是同一個主設備號,到時訪問open 節點的時候就會先調用同一個主設備號的open 函數,
接著,在主設備號的open 函數中,再來分發調用,各個不同次設備號的open 函數。

2.1 CONFIG_SND_MAJOR 主設備號 116

代碼可以參考 sound.c 中的代碼:
主設備號注冊

@\kernel\msm-3.18\include\sound\core.h
#define CONFIG_SND_MAJOR	116	/* standard configuration */  定義主設備號@ \kernel\msm-3.18\sound\core\sound.c
static int major = CONFIG_SND_MAJOR; // 主設備號
module_param(major, int, 0444);
MODULE_PARM_DESC(major, "Major # for sound driver.");static const struct file_operations snd_fops =
{.owner =	THIS_MODULE,.open =		snd_open,.llseek =	noop_llseek,
};static int __init alsa_sound_init(void)
{snd_major = major;snd_ecards_limit = cards_limit;if (register_chrdev(major, "alsa", &snd_fops)) {  // 主冊一個主設備號pr_err("ALSA core: unable to register native major device number %d\n", major);return -EIO;}snd_info_minor_register();return 0;
}
2.2 snd_minors 數組分析

在 snd_register_device_for_dev 函數中,主要作用就是創建不同的次設備號節點,保存在 snd_minors[] 數組中。

其主要是在pcm.c 創建節點時被調用的。
可以發現在代碼中,會根據聲卡號和設備索引號,依次創建 pcmC%iD%ip 和 pcmC%iD%ic 兩個設備節點的名字。
接著,調用 snd_register_device_for_dev 來創建設備節點,傳入換參數就是 設備節點的名字。

@\kernel\msm-3.18\sound\core\pcm.cstatic int snd_pcm_dev_register(struct snd_device *device)
{pcm = device->device_data;err = snd_pcm_add(pcm);for (cidx = 0; cidx < 2; cidx++) {int devtype = -1;switch (cidx) {case SNDRV_PCM_STREAM_PLAYBACK:sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;break;case SNDRV_PCM_STREAM_CAPTURE:sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;break;}/* device pointer to use, pcm->dev takes precedence if* it is assigned, otherwise fall back to card's device* if possible */dev = pcm->dev;/* register pcm */err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);dev = snd_get_device(devtype, pcm->card, pcm->device);if (dev) {err = sysfs_create_groups(&dev->kobj,pcm_dev_attr_groups);put_device(dev);}for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)snd_pcm_timer_init(substream);}list_for_each_entry(notify, &snd_pcm_notify_list, list)notify->n_register(pcm);return 0;
}

在snd_register_device_for_dev() 中,會根據傳入的字符串名字,創建不同的設備節點。
看 snd_register_device_for_dev() 代碼前,我們來看一下snd_minors[] 這個數組。

static struct snd_minor *snd_minors[SNDRV_OS_MINORS];其結構體描述如下:
@ \kernel\msm-3.18\include\sound\core.hstruct snd_minor {int type;			/* SNDRV_DEVICE_TYPE_XXX */  // 聲卡類型: SNDRV_DEVICE_TYPE_PCM_PLAYBACK 和  SNDRV_DEVICE_TYPE_PCM_CAPTURE 兩種int card;			/* card number */ 							//聲卡號int device;			/* device number */							// 設備號const struct file_operations *f_ops;	/* file operations */ 	// 該節點換操作節構體void *private_data;		/* private data for f_ops->open */		// 私有參數struct device *dev;		/* device for sysfs */					// sys 設備節點描述符struct snd_card *card_ptr;	/* assigned card instance */		//聲卡結構體
};

從上面的結構體可以看出,snd_minors中 主要是包含了 card聲卡下 device設備的操作方法 f_ops。
這樣就很清楚了。

通過snd_minors[] 這個 數組,我人能夠找到任意一個 聲卡下的設備 的操作方法。

2.3 pcm設備節點創建代碼

接下來,我們來分析snd_register_device_for_dev() 這個函數,

這個函數主要工作 如下:
step 1. 使用 snd_minor 指針將 要創建的聲卡設備的信息保存下來
step 2. 給聲卡設備分配 次設備號,如果定義了動態分配,則分配次設備號
step 3. 以次設備號為索引,將聲卡設備的信息保存在 snd_minors[minor]數組中。
step 4. 通過 device_create 創建一個 主設備號 majore=116, 次設備號minor 的設備節點,節點名字就是字符串 pcmC%iD%ip 或 pcmC%iD%ic

@ \kernel\msm-3.18\sound\core\sound.c
/*** snd_register_device_for_dev - Register the ALSA device file for the card* @type: the device type, SNDRV_DEVICE_TYPE_XXX* @card: the card instance* @dev: the device index* @f_ops: the file operations* @private_data: user pointer for f_ops->open()* @name: the device file name* @device: the &struct device to link this new device to** Registers an ALSA device file for the given card.* The operators have to be set in reg parameter.** Return: Zero if successful, or a negative error code on failure.*/
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data,const char *name, struct device *device)
{int minor;struct snd_minor *preg;preg = kmalloc(sizeof *preg, GFP_KERNEL);// step 1. 使用 snd_minor  指針將 要創建的聲卡設備的信息保存下來preg->type = type;preg->card = card ? card->number : -1;preg->device = dev;preg->f_ops = f_ops;preg->private_data = private_data;preg->card_ptr = card;// step 2. 給聲卡設備分配 次設備號,如果定義了動態分配,則分配次設備號
#ifdef CONFIG_SND_DYNAMIC_MINORSminor = snd_find_free_minor(type);
#elseminor = snd_kernel_minor(type, card, dev);if (minor >= 0 && snd_minors[minor])minor = -EBUSY;
#endif// step 3. 以次設備號為索引,將聲卡設備的信息保存在 snd_minors[minor]數組中。snd_minors[minor] = preg;// step 4. 通過 device_create 創建一個 主設備號 majore=116, 次設備號minor 的設備節點,節點名字就是字符串 pcmC%iD%ip 或 pcmC%iD%icpreg->dev = device_create(sound_class, device, MKDEV(major, minor),private_data, "%s", name);return 0;
}
2.4 pcm設備節點創建open 過程分析

前面講了pcm設備節點的創建過程,接下來我們來看下如何打開的。

先看下如下代碼,在 snd_fops 文件操作節構全中,包含了 snd_open方法 。
在init 代碼中,是通過 register_chrdev(major, “alsa”, &snd_fops) 來將 major=116 的主設備號 和 snd_fops綁定在一起。

也就是說,凡是打開 設備節點major 為 116 的節點時,都會調用該 snd_fop 的open方法 snd_open()。

@ \kernel\msm-3.18\sound\core\sound.cstatic const struct file_operations snd_fops =
{.owner =	THIS_MODULE,.open =		snd_open,.llseek =	noop_llseek,
};
static int __init alsa_sound_init(void)
{snd_major = major;snd_ecards_limit = cards_limit;if (register_chrdev(major, "alsa", &snd_fops)) {pr_err("ALSA core: unable to register native major device number %d\n", major);return -EIO;}snd_info_minor_register();return 0;
}

在 snd_open() 方法中,整個過程為:
step 1:獲取次設備號
step 2:初始化一個 snd_minor 類型的指針, 和file_operations 類型的操作方法指針
step 3:根據設備的次設備號,從 snd_minors[minor]數組中獲取對應設備的snd_minor 結構體信息
step 4:解析出該設備的 操作方法
step 5:替換文件的操作方法
step 6:調用open 方法

@ \kernel\msm-3.18\sound\core\sound.c
static int snd_open(struct inode *inode, struct file *file)
{// step 1: 獲取次設備號unsigned int minor = iminor(inode);// step 2:初始化一個 snd_minor 類型的指針, 和file_operations 類型的操作方法指針struct snd_minor *mptr = NULL;const struct file_operations *new_fops;// step 3:根據設備的次設備號,從 snd_minors[minor]數組中獲取對應設備的snd_minor 結構體信息。mptr = snd_minors[minor];// step 4:解析出該設備的 操作方法new_fops = fops_get(mptr->f_ops);// step 5: 替換文件的操作方法replace_fops(file, new_fops);// step 6: 調用open 方法if (file->f_op->open)err = file->f_op->open(inode, file);return err;
}
2.5 pcm設備節點 file_operations 介紹

前面,我們說了pcm設備節點的 open() 方法的調用流程,
不知道你有沒有好奇心,是否想進去看下它做了啥呢? 哈哈。

在前面的代碼中,fops 是在 snd_pcm_f_ops[cidx] 中傳遞過來的。

/* register pcm */err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);

我們看下 snd_pcm_f_ops[cidx] 中的定義:

@ \kernel\msm-3.18\sound\core\pcm_native.cconst struct file_operations snd_pcm_f_ops[2] = {{	// SNDRV_PCM_STREAM_PLAYBACK.owner =		THIS_MODULE,.write =		snd_pcm_write,.aio_write =		snd_pcm_aio_write,.open =			snd_pcm_playback_open,.release =		snd_pcm_release,.llseek =		no_llseek,.poll =			snd_pcm_playback_poll,.unlocked_ioctl =	snd_pcm_playback_ioctl,.compat_ioctl = 	snd_pcm_ioctl_compat,.mmap =			snd_pcm_mmap,.fasync =		snd_pcm_fasync,.get_unmapped_area =	snd_pcm_get_unmapped_area,},{	// SNDRV_PCM_STREAM_CAPTURE.owner =		THIS_MODULE,.read =			snd_pcm_read,.aio_read =		snd_pcm_aio_read,.open =			snd_pcm_capture_open,.release =		snd_pcm_release,.llseek =		no_llseek,.poll =			snd_pcm_capture_poll,.unlocked_ioctl =	snd_pcm_capture_ioctl,.compat_ioctl = 	snd_pcm_ioctl_compat,.mmap =			snd_pcm_mmap,.fasync =		snd_pcm_fasync,.get_unmapped_area =	snd_pcm_get_unmapped_area,}
};

可以看出,在 snd_pcm_f_ops 數組中,主要就是定義了 playback 和 capture 的各個操作方法。

2.6 pcm設備節點 snd_pcm_playback_open() 代碼分析

我們以 playback 來分析下 其open 方法: snd_pcm_playback_open()

其主要工作 為:
step 1: 通過nonseekable_open函數,告訴內核,當前文件open 時,是不可 llseek 定位的
step 2: 獲得 snd_minor 結構體中的 privdata 私有數據,其中保存了聲卡的相關信息
step 3: 調用 snd_pcm_open() open 函數,傳參為 pcm 和 SNDRV_PCM_STREAM_CAPTURE;

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{struct snd_pcm *pcm;// step 1: 通過nonseekable_open函數,告訴內核,當前文件open 時,是不可 llseek 定位的int err = nonseekable_open(inode, file);// step 2: 獲得 snd_minor 結構體中的 privdata 私有數據,其中保存了聲卡的相關信息pcm = snd_lookup_minor_data(iminor(inode),SNDRV_DEVICE_TYPE_PCM_PLAYBACK);-------->+	private_data = mreg->private_data;+	return private_data;<-------// step 3: 調用 snd_pcm_open() open 函數,err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);return err;
}
2.7 snd_pcm_open() 代碼分析

主要工作如下:

  1. 將 pcm->card 和 file 添加鏈表
  2. 構造當前進程對應的等待隊列 wait
  3. 將wait 保存在 pcm->open_wait 中
  4. 上鎖
  5. 在while(1) 中打開文件,如果失敗就退出
  6. 在阻塞模式下,設置SO_RCVTIMEO和SO_SNDTIMEO會導致read/write函數返回EAGAIN
    我們此返回 -EAGAIN 說明是正常的,數據還沒寫完
  7. 設置當前進和為可被中斷
  8. 調度,讓更高優先及的任務得到處理,或者讓其他任務得到處理
  9. 等待調度到來,繼續寫播放數據
@ \kernel\msm-3.18\sound\core\pcm_native.cstatic int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
{int err;wait_queue_t wait;// 1. 將 pcm->card 和 file 添加鏈表err = snd_card_file_add(pcm->card, file);if (!try_module_get(pcm->card->module)) {err = -EFAULT;goto __error2;}// 2. 構造當前進程對應的等待隊列 waitinit_waitqueue_entry(&wait, current);// 3. 將wait 保存在 pcm->open_wait 中add_wait_queue(&pcm->open_wait, &wait);// 4. 上鎖mutex_lock(&pcm->open_mutex);while (1) {// 5. 在while(1) 中打開文件,如果失敗就退出err = snd_pcm_open_file(file, pcm, stream);if (err >= 0)break;// 6. 在阻塞模式下,設置SO_RCVTIMEO和SO_SNDTIMEO會導致read/write函數返回EAGAIN// 我們此返回 -EAGAIN 說明是正常的if (err == -EAGAIN) {if (file->f_flags & O_NONBLOCK) {  // 如果是非阻塞模式下,則直接退出,在非阻塞模式下,write或read返回-1,errno為EAGAIN,表示相應的操作還沒執行完成。err = -EBUSY;break;}} elsebreak;// 7. 設置當前進和為可被中斷set_current_state(TASK_INTERRUPTIBLE);mutex_unlock(&pcm->open_mutex);// 8. 調度,讓更高優先及的任務得到處理,或者讓其他任務得到處理schedule();// 9. 等待調度到來,繼續寫播放數據mutex_lock(&pcm->open_mutex);if (pcm->card->shutdown) {err = -ENODEV;break;}if (signal_pending(current)) {err = -ERESTARTSYS;break;}}remove_wait_queue(&pcm->open_wait, &wait);mutex_unlock(&pcm->open_mutex);return err;
}

接下往下看snd_pcm_open_file

static int snd_pcm_open_file(struct file *file,struct snd_pcm *pcm,int stream)
{struct snd_pcm_file *pcm_file;struct snd_pcm_substream *substream;int err;err = snd_pcm_open_substream(pcm, stream, file, &substream);if (err < 0)return err;pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);if (pcm_file == NULL) {snd_pcm_release_substream(substream);return -ENOMEM;}pcm_file->substream = substream;if (substream->ref_count == 1)substream->pcm_release = pcm_release_private;file->private_data = pcm_file;return 0;
}

繼續跟蹤snd_pcm_open_substream

int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,struct file *file,struct snd_pcm_substream **rsubstream)
{struct snd_pcm_substream *substream;int err;err = snd_pcm_attach_substream(pcm, stream, file, &substream);if (err < 0)return err;if (substream->ref_count > 1) {*rsubstream = substream;return 0;}err = snd_pcm_hw_constraints_init(substream);if (err < 0) {pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");goto error;}err = substream->ops->open(substream);if (err < 0)goto error;substream->hw_opened = 1;err = snd_pcm_hw_constraints_complete(substream);if (err < 0) {pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");goto error;}*rsubstream = substream;return 0;error:snd_pcm_release_substream(substream);return err;
}

?substream->ops->open(substream);會調用創建PCM注冊的ops

int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{....../* ASoC PCM operations */if (rtd->dai_link->dynamic) {rtd->ops.open		= dpcm_fe_dai_open;rtd->ops.hw_params	= dpcm_fe_dai_hw_params;rtd->ops.prepare	= dpcm_fe_dai_prepare;rtd->ops.trigger	= dpcm_fe_dai_trigger;rtd->ops.hw_free	= dpcm_fe_dai_hw_free;rtd->ops.close		= dpcm_fe_dai_close;rtd->ops.pointer	= soc_pcm_pointer;} else {rtd->ops.open		= soc_pcm_open;rtd->ops.hw_params	= soc_pcm_hw_params;rtd->ops.prepare	= soc_pcm_prepare;rtd->ops.trigger	= soc_pcm_trigger;rtd->ops.hw_free	= soc_pcm_hw_free;rtd->ops.close		= soc_pcm_close;rtd->ops.pointer	= soc_pcm_pointer;}......
}

2.8 soc_pcm端的open() 代碼分析

MTK平臺是dynamic 調用FE的open,該接口作用有:

1.獲取路由信息,計算當前FE綁定的BE

2.分別Open BE和FE pcm

static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream)
{struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(fe_substream);struct snd_soc_dapm_widget_list *list;int ret;int stream = fe_substream->stream;snd_soc_dpcm_mutex_lock(fe);fe->dpcm[stream].runtime = fe_substream->runtime;ret = dpcm_path_get(fe, stream, &list);if (ret < 0)goto open_end;/* calculate valid and active FE <-> BE dpcms */dpcm_process_paths(fe, stream, &list, 1);ret = dpcm_fe_dai_startup(fe_substream);if (ret < 0)dpcm_fe_dai_cleanup(fe_substream);dpcm_clear_pending_state(fe, stream);dpcm_path_put(&list);
open_end:snd_soc_dpcm_mutex_unlock(fe);return ret;
}

繼續看函數dpcm_fe_dai_startup

其中?dpcm_be_dai_startup 會調用BE端?__soc_pcm_open(be, be_substream);

?? ?/* start the DAI frontend */
ret = __soc_pcm_open(fe, fe_substream);

static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream)
{struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(fe_substream);int stream = fe_substream->stream, ret = 0;dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);ret = dpcm_be_dai_startup(fe, stream);if (ret < 0)goto be_err;dev_dbg(fe->dev, "ASoC: open FE %s\n", fe->dai_link->name);/* start the DAI frontend */ret = __soc_pcm_open(fe, fe_substream);if (ret < 0)goto unwind;fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN;dpcm_runtime_setup_fe(fe_substream);dpcm_runtime_setup_be_format(fe_substream);dpcm_runtime_setup_be_chan(fe_substream);dpcm_runtime_setup_be_rate(fe_substream);ret = dpcm_apply_symmetry(fe_substream, stream);unwind:if (ret < 0)dpcm_be_dai_startup_unwind(fe, stream);
be_err:dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO);if (ret < 0)dev_err(fe->dev, "%s() failed (%d)\n", __func__, ret);return ret;
}

2.9? __soc_pcm_open 代碼分析

?該接口功能如下:

1.?soc_pcm_components_open(substream); 調用component drv ops的open

2.snd_soc_link_startup(substream); 調用dai_link的rtd->dai_link->ops->startup(substream);

3.snd_soc_dai_startup調用cpu和codec 的dai->driver->ops->startup(substream, dai);

static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd,struct snd_pcm_substream *substream)
{struct snd_soc_component *component;struct snd_soc_dai *dai;int i, ret = 0;snd_soc_dpcm_mutex_assert_held(rtd);for_each_rtd_components(rtd, i, component)pinctrl_pm_select_default_state(component->dev);ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream);if (ret < 0)goto err;ret = soc_pcm_components_open(substream);if (ret < 0)goto err;ret = snd_soc_link_startup(substream);if (ret < 0)goto err;/* startup the audio subsystem */for_each_rtd_dais(rtd, i, dai) {ret = snd_soc_dai_startup(dai, substream);if (ret < 0)goto err;}/* Dynamic PCM DAI links compat checks use dynamic capabilities */if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm)goto dynamic;/* Check that the codec and cpu DAIs are compatible */soc_pcm_init_runtime_hw(substream);soc_pcm_update_symmetry(substream);ret = soc_hw_sanity_check(substream);if (ret < 0)goto err;soc_pcm_apply_msb(substream);/* Symmetry only applies if we've already got an active stream. */for_each_rtd_dais(rtd, i, dai) {ret = soc_pcm_apply_symmetry(substream, dai);if (ret != 0)goto err;}
dynamic:snd_soc_runtime_activate(rtd, substream->stream);ret = 0;
err:if (ret < 0) {soc_pcm_clean(rtd, substream, 1);dev_err(rtd->dev, "%s() failed (%d)", __func__, ret);}return ret;
}

?整體流程圖如下:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96450.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96450.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96450.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

tp5的tbmember表閉包查詢 openid=‘abc‘ 并且(wx_unionid=null或者wx_unionid=‘‘)

閉包查詢 tbmember表閉包查詢查詢 openid‘abc并且islose0并且islogout0并且&#xff08;wx_unionidnull或者wx_unionid’&#xff09; Db::table(tbmember)->where([openid>abc,islose>0,islogout>0])->where(function ($query){$query->where(wx_unioni…

邪修實戰系列(3)

1、第一階段邪修實戰總覽&#xff08;9.1-9.30&#xff09; 把第一階段&#xff08;基礎夯實期&#xff09;的學習計劃拆解成極具操作性的每日行動方案。這個計劃充分利用我“在職學習”的特殊優勢&#xff0c;強調“用輸出倒逼輸入”&#xff0c;確保每一分鐘的學習都直接服務…

【GD32】ROM Bootloader、自定義Bootloader區別

Bootloader是應用程序跑起來之前&#xff0c;用于初始化的一段程序&#xff0c;它分為兩種&#xff0c;ROM Bootloader、自定義Bootloader。GD32芯片出廠時預燒錄在ROM中的Bootloader&#xff08;以下簡稱ROM Bootloader&#xff09;和自己編寫的Bootloader&#xff08;以下簡稱…

Linux防火墻-Firewalld

一、 概述 按表現形式劃分&#xff1a; 軟件防火墻&#xff1a; 集成在系統內部&#xff0c;Linux系統&#xff1a; iptables、firewalld、ufw&#xff1b; windows系統下&#xff1a; windows defender 硬件防火墻&#xff1a; 華為防火墻、思科防火墻、奇安信防火墻、深信服防…

【Qt】PyQt、原生QT、PySide6三者的多方面比較

目錄 引言 一、基本定義 二、核心對比維度 1. 編程語言與開發效率 2. 功能與 API 兼容性 3. 性能表現 4. 許可證與商業使用 5. 社區與文檔支持 三、遷移與兼容性 四、適用場景推薦 五、總結對比表 總結 引言 PySide6、PyQt&#xff08;通常指 PyQt5/PyQt6&#xf…

JavaWeb站內信系統 - 技術設計文檔

1. 系統概述1.1 項目背景本系統旨在為企業或社區平臺提供一套完整的站內信解決方案&#xff0c;支持用戶之間的消息發送、接收、管理等功能&#xff0c;提升用戶間的溝通效率。1.2 設計目標實現用戶間消息發送和接收支持一對一和一對多消息發送提供消息狀態跟蹤&#xff08;已讀…

Java基礎 9.10

1.System類常見方法和案例exit&#xff1a;退出當前程序arraycopy&#xff1a;復制數組元素&#xff0c;比較適合底層調用&#xff0c;一般使用 Arrays.copyOf 完成復制數組int[] src{1,2,3};int[] dest new int[3]; System.arraycopy(src, 0, dest, 0, 3);currentTimeMilens&…

詳解flink性能優化

1. 簡介 Apache Flink是一個強大的流處理框架&#xff0c;其性能很大程度上取決于內存的使用效率。在大規模數據處理場景中&#xff0c;合理的內存配置和優化可以顯著提升Flink作業的性能和穩定性。本文將深入探討Flink內存優化的各個方面&#xff0c;包括狀態后端選擇、內存配…

VueFlow的箭頭怎么調整

正好最近用到了VueFlow組件&#xff0c;發現箭頭默認樣式太小&#xff0c;無法體現流程展示&#xff0c;因此翻閱相關資料得出下列方法&#xff0c;有什么更好的方法&#xff0c;大家可以推薦推薦&#xff0c;謝謝。方法1&#xff1a;通過邊&#xff08;Edge&#xff09;的樣式…

【Python】S1 基礎篇 P9 文件處理與異常處理技術

目錄文件讀取操作讀取文件的全部內容相對路徑和絕對路徑逐行訪問文件內容文件寫入操作寫入單行內容寫入多行內容結構化數據的存儲異常處理機制理解異常的工作原理ZeroDivisionError異常示例try-except語句塊的使用else語句塊的正確使用靜默失敗的合理應用本文將深入探討Python中…

分布式事務實戰手冊:從四場業務災難看方案選型與落地陷阱

在分布式系統的穩定性戰役中&#xff0c;數據一致性問題如同潛伏的暗礁。某生鮮電商因分布式事務設計缺陷&#xff0c;在春節促銷期間出現"下單成功但無庫存發貨"的悖論&#xff0c;3小時內產生2300筆無效訂單&#xff0c;客服投訴量激增300%&#xff1b;某銀行轉賬系…

Java算法題中的輸入輸出流

在Java算法題中&#xff0c;處理輸入輸出主要依賴系統流&#xff08;System.in和System.out&#xff09;&#xff0c;常用的方法總結如下&#xff1a; 一、輸入方法&#xff08;讀取系統輸入&#xff09; 主要通過java.util.Scanner類或BufferedReader類實現&#xff0c;適用于…

墨水屏程序

EPD Reader 基于ESP32-C3的電子墨水屏閱讀器&#xff0c;支持ap 配網、sntp 時間同步、txt閱讀、天氣預報、顯示節假日信息、農歷顯示、自動休眠、web配置等功能。這是在另一個項目 一個rust embassy esp32c3 的練習項目-CSDN博客的基礎上修改的 。 界面比較粗糙&#xff0c;以…

Git 創建 SSH 密鑰

1.生成 SSH 密鑰 打開 Git Bash ssh-keygen -t ed25519 -C "your_email@example.com" 把 ”your_email@example.com“ 改成再 github 注冊的郵箱 系統會提示您三次輸入: 第一個提示:Enter file in which to save the key (/c/Users/86189/.ssh/id_ed25519): 直接…

當前 AI 的主流應用場景

當前AI技術已深度滲透至社會各領域,2025年的主流應用場景呈現出行業垂直化、交互自然化、決策自主化三大特征。以下從六大核心領域展開分析,結合最新技術突破與規模化落地案例,揭示AI如何重塑人類生產生活范式: 一、智能辦公與生產力革命 AI正從工具升級為「數字同事」,…

EI會議:第六屆電信、光學、計算機科學國際會議(TOCS 2025)

第六屆電信、光學、計算機科學國際會議&#xff08;TOCS 2025&#xff09;定于11月21-23日在中國南陽舉行&#xff0c;本屆會議以“電信、光學、計算機科學”為主題&#xff0c;旨在為相關領域的專家和學者提供一個探討行業熱點問題&#xff0c;促進科技進步&#xff0c;增加科…

回歸預測 | MATLAB基于GRU-Attention的多輸入單輸出回歸預測

代碼是一個基于 MATLAB 的深度學習時間序列預測模型,結合了 GRU(門控循環單元)和自注意力機制(Self-Attention),用于回歸預測任務。 一、主要功能 使用 GRU + Self-Attention 神經網絡模型對時間序列數據進行回歸預測,評估模型在訓練集和測試集上的性能,并可視化預測結…

【JavaEE】(24) Linux 基礎使用和程序部署

一、Linux 背景知識 Linux 的第一個版本開發者是 Linus&#xff0c;所以部分人會叫“林納斯”。Linux 只是一個開源的操作系統內核&#xff0c;有些公司/開源組織基于 Linux 內核&#xff0c;配套了不同的應用程序&#xff0c;構成不同的操作系統&#xff08;比如 vivo、&#…

視覺SLAM第9講:后端1(EKF、非線性優化)

目標&#xff1a; 1.理解后端的概念&#xff1b; 2.理解以EKF為代表的濾波器后端的工作原理&#xff1b; 3.理解非線性優化的后端&#xff0c;明白稀疏性是如何利用的&#xff1b; 4.使用g2o和Ceres實際操作后端優化。 9.1 概述 9.1.1 狀態估計的概率解釋 1.后端優化引出 前段…

樓宇自控系統監控建筑變配電系統:功效體現在安全與節能層面

建筑變配電系統是保障建筑電力供應的 “心臟”&#xff0c;負責將外界高壓電轉化為建筑內設備可使用的低壓電&#xff0c;為暖通、照明、電梯等核心系統供電。傳統變配電管理依賴人工巡檢&#xff0c;不僅存在 “監測滯后、故障難預判” 的安全隱患&#xff0c;還因無法精準調控…