從零寫一個ALSA聲卡驅動學習(1)

前言:

本文檔描述了如何編寫 ALSA(高級 Linux 音頻架構)驅動程序。文檔主要聚焦于 PCI 聲卡的實現。對于其他類型的設備,可能會使用不同的 API。不過,至少 ALSA 的內核 API 是一致的,因此本文檔在編寫這些驅動時仍然具有一定的參考價值。 本指南面向那些已經具備足夠 C 語言技能并且掌握基本 Linux 內核編程知識的開發者。本文檔不會講解 Linux 內核編程的一般性內容,也不會涉及底層驅動實現的細節。它僅介紹在 ALSA 框架下編寫 PCI 聲卡驅動的標準方法。

內容參考學習鏈接文檔:

https://kernel.org/doc/html/latest/sound/kernel-api/writing-an-alsa-driver.html#header-files

File Tree Structure:

下面的文件結構是Linux內核關于sound目錄下的介紹:

sound/core/oss/seq/oss/include/drivers/mpu401/opl3/i2c/synth/emux/pci/(cards)/isa/(cards)/arm/ppc/sparc/usb/pcmcia /(cards)/soc/oss
  • core 目錄 : 該目錄包含 ALSA 驅動的中間層,是 ALSA 驅動的核心。此目錄中存放的是 ALSA 的原生模塊。其子目錄包含了不同的模塊,并依賴于內核配置選項。

  • core/oss ?: 該目錄存放 OSS PCM 和混音器(mixer)仿真模塊的代碼。由于 OSS 的 rawmidi 仿真部分非常小,因此被直接包含在 ALSA 的 rawmidi 代碼中。序列器(sequencer)相關的 OSS 仿真代碼則位于 core/seq/oss 目錄(見下文)。

  • core/seq ?: 該目錄及其子目錄存放 ALSA 序列器(sequencer)相關代碼。它包含了序列器核心以及主要模塊,如 snd-seq-midi、snd-seq-virmidi 等。這些模塊僅在內核配置中啟用了 CONFIG_SND_SEQUENCER 時才會被編譯。

  • core/seq/oss ?: 此目錄包含 OSS 序列器仿真代碼。

  • include 目錄 ?: 該目錄用于存放 ALSA 驅動對用戶空間公開的頭文件,或供多個不同目錄中的文件共享使用。一般來說,私有頭文件不應放在這個目錄中,但你仍可能會在其中看到一些私有頭文件,這是歷史遺留問題所致。:)

  • drivers 目錄 ?:該目錄包含在不同平臺間共享的驅動代碼,因此它們不應具有架構相關性。例如,dummy PCM 驅動和串口 MIDI 驅動都位于此目錄。其子目錄中存放的是與總線或 CPU 架構無關的組件代碼。 drivers/mpu401 包含 MPU401 和 MPU401-UART 模塊。 drivers/opl3 與 opl4 包含 OPL3 和 OPL4 FM 合成器相關代碼。

  • i2c 目錄 ?:該目錄包含 ALSA 的 i2c 相關組件。雖然 Linux 系統本身有標準的 i2c 層,但某些聲卡只需要簡單的操作,而標準的 i2c API 過于復雜,因此 ALSA 對某些聲卡實現了自己的 i2c 代碼。

  • synth 目錄 ?:此目錄包含中間層的合成器模塊。目前,只有 Emu8000/Emu10k1 合成器驅動存放在 synth/emux 子目錄中。

  • pci 目錄 ?:此目錄及其子目錄存放 PCI 聲卡的頂層驅動模塊,以及與 PCI 總線相關的專用代碼。如果驅動只包含單個源文件,則直接放在 pci 目錄下;如果驅動包含多個源文件,則會單獨創建子目錄(例如 emu10k1、ice1712)。

  • isa 目錄 ?: 此目錄及其子目錄存放 ISA 聲卡的頂層驅動模塊。

  • arm、ppc 和 sparc 目錄 ?: 這些目錄用于存放特定于某一架構的頂層聲卡驅動模塊。

  • usb 目錄 ?: 該目錄包含 USB 音頻驅動。USB MIDI 驅動已經整合進了 USB 音頻驅動中。

  • pcmcia 目錄 ?: 該目錄用于存放 PCMCIA(尤其是 PCCard)驅動。由于 CardBus 的 API 與標準 PCI 卡相同,因此其驅動位于 pci 目錄中。

  • soc 目錄 ?: 該目錄包含 ASoC(ALSA SoC,系統級芯片)層的代碼,包括 ASoC 核心、編解碼器(codec)和 machine 驅動等。

  • oss 目錄 ?: 該目錄包含 OSS/Lite 代碼。截止目前,除了 m68k 架構下的 dmasound 之外,其他代碼均已被移除。

basic Flow for PCI Drivers:

PCI 聲卡驅動的最小實現流程如下:

  1. 定義 PCI ID 表(參見“PCI 條目”部分)。

  2. 創建 probe 回調函數(用于設備檢測和初始化)。

  3. 創建 remove 回調函數(用于設備移除時的清理操作)。

  4. 創建一個 struct pci_driver 結構體,包含上述三個函數指針(即:PCI ID 表、probe 和 remove 函數)。

  5. 創建初始化函數,該函數調用 pci_register_driver() 來注冊上面定義的 pci_driver 表。

  6. 創建退出函數,該函數調用 pci_unregister_driver() 來注銷該驅動。

Full Code Example:

下面展示了一個代碼示例。目前有些部分尚未實現,但會在接下來的章節中補充完成。 在 snd_mychip_probe() 函數中的注釋行里標注的數字,對應的是下一節中將詳細解釋的內容。

#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/initval.h>/* module parameters (see?"Module Parameters") */
/* SNDRV_CARDS: maximum number of cards supported by this module */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool?enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* definition of the chip-specific record */
struct mychip {struct snd_card *card;/* the rest of the implementation will be?in?section*?"PCI Resource Management"*/
};/* chip-specific destructor* (see?"PCI Resource Management")*/
static int snd_mychip_free(struct mychip *chip)
{.... /* will be implemented later... */
}/* component-destructor* (see?"Management of Cards and Components")*/
static int snd_mychip_dev_free(struct snd_device *device)
{return?snd_mychip_free(device->device_data);
}/* chip-specific constructor* (see?"Management of Cards and Components")*/
static int snd_mychip_create(struct snd_card *card,struct pci_dev *pci,struct mychip **rchip)
{struct mychip *chip;int err;static const struct snd_device_ops ops = {.dev_free = snd_mychip_dev_free,};*rchip = NULL;/* check PCI availability here* (see?"PCI Resource Management")*/..../* allocate a chip-specific data with zero filled */chip = kzalloc(sizeof(*chip), GFP_KERNEL);if?(chip == NULL)return?-ENOMEM;chip->card = card;/* rest of initialization here; will be implemented* later, see?"PCI Resource Management"*/....err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);if?(err < 0) {snd_mychip_free(chip);return?err;}*rchip = chip;return?0;
}/* constructor -- see?"Driver Constructor"?sub-section */
static int snd_mychip_probe(struct pci_dev *pci,const struct pci_device_id *pci_id)
{static int dev;struct snd_card *card;struct mychip *chip;int err;/* (1) */if?(dev >= SNDRV_CARDS)return?-ENODEV;if?(!enable[dev]) {dev++;return?-ENOENT;}/* (2) */err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card);if?(err < 0)return?err;/* (3) */err = snd_mychip_create(card, pci, &chip);if?(err < 0)goto error;/* (4) */strcpy(card->driver,?"My Chip");strcpy(card->shortname,?"My Own Chip 123");sprintf(card->longname,?"%s at 0x%lx irq %i",card->shortname, chip->port, chip->irq);/* (5) */.... /* implemented later *//* (6) */err = snd_card_register(card);if?(err < 0)goto error;/* (7) */pci_set_drvdata(pci, card);dev++;return?0;error:snd_card_free(card);return?err;
}/* destructor -- see the?"Destructor"?sub-section */
static void snd_mychip_remove(struct pci_dev *pci)
{snd_card_free(pci_get_drvdata(pci));
}

驅動構造函數:

PCI 驅動的真正構造函數是 probe 回調函數。由于 PCI 設備可能是熱插拔設備,因此 probe 回調函數及其調用的其他組件構造函數不能使用 __init 前綴。 在 probe 回調函數中,通常會使用以下流程:

  • 1)檢查并遞增設備索引。

static int dev;
....
if?(dev >= SNDRV_CARDS)return?-ENODEV;
if?(!enable[dev]) {dev++;return?-ENOENT;
}

其中 enable[dev] 是模塊參數選項。 每次調用 probe 回調時,都要檢查該設備是否可用。如果不可用,則只需遞增設備索引并返回。dev 變量稍后(在第 7 步)也會繼續遞增。

  • 2)創建一個 sound card 實例

struct snd_card *card;
int err;
....
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card);

詳細內容將在“聲卡與組件的管理”章節中進行說明。

  • 3)創建主組件 在這一部分,將分配 PCI 資源

struct mychip *chip;
....
err = snd_mychip_create(card, pci, &chip);
if?(err < 0)goto error;

詳細內容將在“PCI 資源管理”章節中進行說明。 當出現錯誤時,probe 函數需要進行錯誤處理。在本示例中,我們采用統一的錯誤處理路徑,該路徑被放置在函數的末尾:

error:snd_card_free(card);return?err;

于每個組件都可以被正確釋放,因此在大多數情況下,只需調用一次 snd_card_free() 就足夠了。

  • 4)設置驅動的 ID 和名稱字符串。

strcpy(card->driver,?"My Chip");
strcpy(card->shortname,?"My Own Chip 123");
sprintf(card->longname,?"%s at 0x%lx irq %i",card->shortname, chip->port, chip->irq);

driver 字段保存芯片的最小 ID 字符串。這個 ID 會被 alsa-lib 的配置器使用,因此應保持簡潔但唯一。即使是同一個驅動,也可以使用不同的 driver ID 來區分不同類型芯片的功能。 shortname 字段是一個簡短但更具描述性的名稱字符串,用于展示。 longname 字段包含的是將在 /proc/asound/cards 中顯示的信息。

  • 5)創建其他組件,如混音器、MIDI 等 在此步驟中定義基本組件,例如 PCM、混音器(如 AC97)、MIDI(如 MPU-401)以及其他接口。如果需要定義 proc 文件,也應在這里進行。

  • 6)注冊該聲卡實例

err = snd_card_register(card);
if?(err < 0)goto error;

這一部分也將在“聲卡與組件的管理”章節中進行說明。

  • 7) 設置 PCI 驅動的數據,并返回 0

pci_set_drvdata(pci, card);
dev++;
return?0;

在上述步驟中,聲卡記錄被保存了下來。該指針同樣會在 remove 回調函數 和 電源管理回調函數 中使用。

Destructor(析構函數):

析構函數,即 remove 回調函數,僅需釋放聲卡實例即可。隨后,ALSA 中間層會自動釋放所有附加的組件。 通常只需調用一行代碼即可:

static void snd_mychip_remove(struct pci_dev *pci)
{snd_card_free(pci_get_drvdata(pci));
}

上述代碼的前提是:聲卡指針已通過 PCI 驅動的數據接口(即 pci_set_drvdata())保存。

Header Files:

對于上述示例,至少需要包含以下頭文件:

#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/initval.h>

最后一個頭文件(指 <linux/moduleparam.h>)僅在源碼中定義了模塊參數時才是必須的。如果代碼被拆分為多個文件,那么沒有模塊參數定義的文件就不需要包含該頭文件。 除了這些頭文件之外:

  • 如果要處理中斷,需要包含 <linux/interrupt.h>;

  • 如果需要進行 I/O 操作訪問,需要包含 <linux/io.h>;

  • 如果使用了 mdelay() 或 udelay() 延時函數,還需要包含 <linux/delay.h>。

ALSA 接口,例如 PCM 和控制接口(control API),則定義在其他的 <sound/xxx.h> 頭文件中。 這些頭文件必須在 <sound/core.h> 之后包含。

聲卡與組件的管理(Management of Cards and Components):

  • Card Instance

對于每一塊聲卡,必須分配一個 “card” 記錄。 “card” 記錄是聲卡的核心管理結構,它負責管理該聲卡上的所有設備(組件),例如 PCM、混音器(Mixer)、MIDI、合成器等。同時,該記錄還保存聲卡的 ID 和名稱字符串,管理與之相關的 proc 文件入口,控制電源管理狀態以及處理熱插拔斷開等情況。卡記錄中的組件列表用于在驅動銷毀時正確釋放所有資源。 如前所述,要創建一個聲卡實例,可以調用 snd_card_new() 函數:

struct snd_card *card;
int err;
err = snd_card_new(&pci->dev, index, id, module, extra_size, &card);

該函數接受六個參數:父設備指針、聲卡索引號、ID 字符串、模塊指針(通常為 THIS_MODULE)、額外數據空間的大小,以及用于返回聲卡實例的指針。 其中,extra_size 參數用于為芯片相關的私有數據分配 card->private_data 空間。需要注意的是,這塊私有數據是由 snd_card_new() 函數自動分配的。 第一個參數是 struct device 的指針,用于指定父設備。對于 PCI 設備,通常傳入 &pci->dev

  • Components

在創建好聲卡之后,你可以將各個組件(設備)附加到該聲卡實例上。在 ALSA 驅動中,每個組件由一個 struct snd_device 對象表示。組件可以是一個 PCM 實例、控制接口、Raw MIDI 接口等。每個這樣的實例對應一個組件條目。 組件可以通過 snd_device_new() 函數來創建:

snd_device_new(card, SNDRV_DEV_XXX, chip, &ops);

該函數接收以下參數:聲卡指針、設備級別(SNDRV_DEV_XXX)、數據指針以及回調函數指針(&ops)。

其中,設備級別用于定義組件的類型,并決定其注冊和注銷的順序。對于大多數組件,設備級別已在 ALSA 中預定義。對于用戶自定義的組件,可以使用 SNDRV_DEV_LOWLEVEL。

此函數本身不會分配數據空間,數據需要在調用之前手動分配,并將其指針作為參數傳入。在上述示例中,該指針(如 chip)會作為該實例的標識符使用。

對于 ALSA 中預定義的組件(如 AC97、PCM 等),它們在各自的構造函數內部會調用 snd_device_new()。組件的析構函數由回調函數中的指針指定,因此開發者無需手動調用組件的析構函數。

如果你希望創建自定義組件,需要在 ops 結構中設置 dev_free 回調指針為對應的析構函數,這樣在調用 snd_card_free() 時該組件就能自動被釋放。 接下來的示例將展示如何實現與芯片相關的私有數據結構。

  • Chip-Specific Data

與芯片相關的特定信息,例如 I/O 端口地址、資源指針或中斷號(irq number),會被存儲在芯片專用的數據結構(record)中:

struct mychip {....
};

一般來說,分配芯片記錄有兩種方式。

  • 通過 snd_card_new() 分配。

如前所述,你可以將額外數據的長度作為 snd_card_new() 的第 5 個參數傳入,例如:

err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,sizeof(struct mychip), &card);

struct mychip 是芯片記錄的結構體類型。 作為返回值,分配好的記錄可以通過以下方式訪問:

struct mychip *chip = card->private_data;

使用這種方法,你無需進行兩次內存分配。該記錄會隨著聲卡實例一起被釋放。

  • Allocating an extra device.

在通過 snd_card_new() 分配聲卡實例(第 4 個參數設為 0)之后,調用 kzalloc():

struct snd_card *card;
struct mychip *chip;
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card);
.....
chip = kzalloc(sizeof(*chip), GFP_KERNEL);

芯片記錄結構體中至少應包含一個字段用于保存聲卡指針:

struct mychip {struct snd_card *card;....
};

然后,在返回的芯片實例中設置該聲卡指針:

chip->card = card;

接下來,初始化各個字段,并使用指定的 ops 將該芯片記錄作為一個低層設備(low-level device)進行注冊:

static const struct snd_device_ops ops = {.dev_free = ? ? ? ?snd_mychip_dev_free,
};
....
snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);

snd_mychip_dev_free() 是設備的析構函數,它將調用真正的析構操作:

static int snd_mychip_dev_free(struct snd_device *device)
{return?snd_mychip_free(device->device_data);
}

其中,snd_mychip_free() 才是真正的析構函數。 這種方法的缺點是:代碼量明顯更大。 但它的優點在于:你可以通過在 snd_device_ops 中設置相關回調函數,在聲卡注冊和斷開連接時觸發自定義的回調操作。 關于聲卡的注冊與斷開連接,請參見下文的子章節。

注冊與釋放

在所有組件都分配完成后,通過調用 snd_card_register() 來注冊聲卡實例。從這一刻起,設備文件的訪問才會被啟用。

也就是說,在調用 snd_card_register() 之前,外部無法訪問這些組件,因此是安全的。

如果該函數調用失敗,應在調用 snd_card_free() 釋放聲卡資源后退出 probe 函數。

要釋放聲卡實例,只需調用 snd_card_free()。

如前所述,該調用會自動釋放所有附加的組件。

對于支持熱插拔的設備,可以使用 snd_card_free_when_closed()。 該函數將在所有設備文件被關閉后,再延遲銷毀聲卡實例。

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

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

相關文章

鏈結構與工作量證明7??:用 Go 實現比特幣的核心機制

鏈結構與工作量證明:用 Go 實現比特幣的核心機制 如果你用 Go 寫過區塊、算過哈希,也大致理解了非對稱加密、數據序列化這些“硬核知識”,那么恭喜你,現在我們終于可以把這些拼成一條完整的“區塊鏈”。 不過別急,這一節我們重點搞懂兩件事: 區塊之間是怎么連接成“鏈”…

深入理解 React 樣式方案

React 的樣式方案較多,在應用開發初期,開發者需要根據項目業務具體情況選擇對應樣式方案。React 樣式方案主要有: 1. 內聯樣式 2. module css 3. css in js 4. tailwind css 這些方案中,均有各自的優勢和缺點。 1. 方案優劣勢 1. 內聯樣式: 簡單直觀,適合動態樣式和…

YOLO電力物目標檢測訓練

最近需要進行電力物檢測相關的業務&#xff0c;因此制作了一個電力物數據集&#xff0c;使用YOLO目標檢測方法進行實驗&#xff0c;記錄實驗過程如下&#xff1a; 數據集標注 首先需要對電力物相關設備進行標注&#xff0c;這里我們選用labelme進行標注&#xff0c;使用無人機…

從阿里云域名解析異常事件看下域名解析過程

近日阿里云核心域名aliyuncs.com解析異常&#xff0c;涉及眾多依賴阿里云服務的網站和應用&#xff0c;故障從發現到修復耗時5個多小時。本文簡要分析下整個事件的過程&#xff0c;并分析域名解析的過程&#xff0c;了解根域名服務器在其中的作用&#xff0c;以了解。 1、aliyu…

應用分享 | 精準生成和時序控制!AWG在確定性三量子比特糾纏光子源中的應用

在量子技術飛速發展的今天&#xff0c;實現高效穩定的量子態操控是推動量子計算、量子通信等領域邁向實用化的關鍵。任意波形發生器&#xff08;AWG&#xff09;作為精準信號控制的核心設備&#xff0c;在量子實驗中發揮著不可或缺的作用。丹麥哥本哈根大學的研究團隊基于單個量…

基于規則的自然語言處理

基于規則的自然語言處理 規則方法形態還原&#xff08;針對英語、德語、法語等&#xff09;中文分詞切分歧義分詞方法歧義字段消歧方法分詞帶來的問題 詞性標注命名實體分類機器翻譯規則方法的問題 規則方法 以規則形式表示語言知識&#xff0c;強調人對語言知識的理性整理&am…

Vue Fragment vs React Fragment

文章目錄 前言&#x1f9e9; 一、概念對比&#xff1a;Vue Fragment vs React Fragment&#x1f4e6; 二、使用示例對比? Vue 3 中使用 Fragment? React 中使用 Fragment &#x1f50d; 三、差異解析1. **使用方式**2. **傳遞屬性&#xff08;如 key&#xff09;**3. **插槽系…

3D圖像渲染和threejs交互坐標系入門知識整理

1. Games101 b站上面就有&#xff0c;看到第9節課基本對于圖形渲染的原理和渲染過程有所了解。然后就可以使用openGL和GLSL。 點輸入->投影到二維&#xff08;生成三角形面&#xff09;->光柵化為像素->z-buffer深度緩存判斷層級->著色shading 2. openGL和GLSL 參…

跨平臺架構區別

文章目錄 重編譯時輕運行時&#xff08;uniapp&#xff09;輕編譯時重運行時&#xff08;Taro&#xff09; 重編譯時輕運行時&#xff08;uniapp&#xff09; 對 vue 語法直接進行編譯轉換成對應平臺代碼&#xff0c;再通過添加運行時代碼去補充能力&#xff0c;比如 nextTick…

windows系統MySQL安裝文檔

概覽&#xff1a;本文討論了MySQL的安裝、使用過程中涉及的解壓、配置、初始化、注冊服務、啟動、修改密碼、登錄、退出以及卸載等相關內容&#xff0c;為學習者提供全面的操作指導。關鍵要點包括&#xff1a; 解壓 &#xff1a;下載完成后解壓壓縮包&#xff0c;得到MySQL 8.…

macOS多出來了:Google云端硬盤、YouTube、表格、幻燈片、Gmail、Google文檔等應用

文章目錄 問題現象問題原因解決辦法 問題現象 macOS啟動臺&#xff08;Launchpad&#xff09;多出來了&#xff1a;Google云端硬盤、YouTube、表格、幻燈片、Gmail、Google文檔等應用。 問題原因 很明顯&#xff0c;都是Google家的辦公全家桶。這些應用并不是通過獨立安裝的…

HarmonyOS 應用開發學習記錄 - 從Windows開發者視角看鴻蒙開發

起始 2024年6月21日召開的華為開發者大會2024上宣布Harmony OS NEXT&#xff08;即鴻蒙星河版&#xff09;面向開發者啟動Beta版&#xff0c;這也被人們稱為“純血鴻蒙”&#xff0c;它基于鴻蒙內核&#xff0c;不再兼容安卓開發的APP應用。 時至今日近一年了&#xff0c;我也有…

MySQL 事務管理與鎖優化:確保數據一致性和并發性

在多用戶并發訪問的數據庫系統中,如何確保數據的**一致性(Consistency)和并發性(Concurrency)**是一個核心挑戰。**事務(Transaction)和鎖(Lock)**是 MySQL 應對這一挑戰的兩大利器。事務保證了操作的原子性、一致性、隔離性和持久性,而鎖機制則在并發環境下協調不同…

OpenPrompt 有沒有實現連續提示詞和提手動示詞一起優化的

OpenPrompt 有沒有實現連續提示詞和提手動示詞一起優化的 OpenPrompt 中連續提示詞與手動提示詞的混合優化 OpenPrompt 確實支持同時優化連續提示詞(Soft Prompt)和手動設計的離散提示詞(Manual Prompt)。這種混合優化策略可以結合兩者的優勢: 連續提示詞:通過梯度下降…

Android添加語言列表

方式一 frameworks\base\packages\SettingsProvider\src\com\android\providers\settings\DatabaseHelper.java Settings.System.putString(context.getContentResolver(),Settings.System.SYSTEM_LOCALES, "ru-RU,en-US"); 方式2 packages/apps/Settings/src/co…

解決uniapp開發app map組件最高層級 遮擋自定義解決底部tabbar方法

subNvue&#xff0c;是 vue 頁面的原生子窗體&#xff0c;把weex渲染的原生界面當做 vue 頁面的子窗體覆蓋在頁面上。它不是全屏頁面&#xff0c;它給App平臺vue頁面中的層級覆蓋和原生界面自定義提供了更強大和靈活的解決方案。它也不是組件&#xff0c;就是一個原生子窗體。 …

如何保障服務器的安全

如何保障服務器的安全 以下是保障服務器安全的核心措施及實施建議&#xff1a; 一、基礎設施層防護 物理安全 機房設置防火/防水/防雷系統&#xff0c;部署門禁監控設備。 服務器固定于抗震機架&#xff0c;避免物理損壞。 網絡防護 防火墻規則&#xff1a;僅開放業務必要端…

C語言 學習 C程序的內存模型 2025年6月10日08:55:13

堆棧與內存管理 堆棧(Stack) : 后進先出(LIFO) 線性數據結構 包含壓棧(Push) ,彈棧(Pop) 用途:臨時存儲數據(函數調用,局部變量) 管理:由系統自動分配和回收 速度快 ,容量有限! 堆棧代碼示例: //堆棧示例 :局部變量 void getText() {int text20;//儲存在堆棧中 } 內存管理…

CppCon 2015 學習:Implementing class properties effectively

這段內容講的是C中“屬性”&#xff08;Property&#xff09;的實現及其設計理念&#xff0c;并結合一個實際類Text來說明。中文理解如下&#xff1a; 關于“屬性”&#xff08;Property&#xff09; 屬性&#xff1a;介于類的字段&#xff08;field&#xff09;和方法&#…

[electron]預腳本不顯示內聯script

script-src self 是 Content Security Policy (CSP) 中的一個指令&#xff0c;它的作用是限制加載和執行 JavaScript 腳本的來源。 具體來說&#xff1a; self 表示 當前源。也就是說&#xff0c;只有來自當前網站或者當前頁面所在域名的 JavaScript 腳本才被允許執行。"…