2. PCM设备创建与销毁
任务目标
在Linux ALSA驱动中,通过snd_pcm_new()动态创建PCM设备,并在驱动卸载时通过snd_pcm_free()正确销毁,理解两者在驱动生命周期中的调用时机与配对关系。
核心概念
snd_pcm_new()在声卡初始化阶段(通常位于probe回调中)创建PCM实例,此时PCM设备尚未对用户空间可见;需再调用snd_pcm_set_ops()绑定操作集并调用snd_device_register()完成注册。销毁时,snd_pcm_free()会释放PCM实例及其关联的substream,通常在remove回调中调用。若PCM已注册,需先通过snd_device_free()注销设备,再调用snd_pcm_free()释放内存。
最小可复现实现
以下代码展示一个完整的最小驱动模块,创建单个PCM播放设备(8个substream),并在卸载时正确销毁:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
static struct snd_card *card;
static struct snd_pcm *pcm;
static int dummy_pcm_open(struct snd_pcm_substream *substream)
{
return 0;
}
static int dummy_pcm_close(struct snd_pcm_substream *substream)
{
return 0;
}
static struct snd_pcm_ops dummy_pcm_ops = {
.open = dummy_pcm_open,
.close = dummy_pcm_close,
};
static int dummy_probe(struct platform_device *pdev)
{
int ret;
/* 1. 创建声卡 */
ret = snd_card_new(&pdev->dev, -1, "dummy", THIS_MODULE, 0, &card);
if (ret < 0)
return ret;
/* 2. 创建PCM设备:播放方向,8个substream */
ret = snd_pcm_new(card, "dummy PCM", 0, 8, 0, &pcm);
if (ret < 0) {
snd_card_free(card);
return ret;
}
/* 3. 绑定操作集 */
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dummy_pcm_ops);
/* 4. 注册声卡(PCM设备随之对用户空间可见) */
ret = snd_card_register(card);
if (ret < 0) {
snd_pcm_free(pcm);
snd_card_free(card);
return ret;
}
return 0;
}
static void dummy_remove(struct platform_device *pdev)
{
/* 销毁顺序:先释放PCM,再释放声卡 */
if (pcm) {
snd_pcm_free(pcm);
pcm = NULL;
}
if (card) {
snd_card_free(card);
card = NULL;
}
}
static struct platform_driver dummy_driver = {
.probe = dummy_probe,
.remove = dummy_remove,
.driver = {
.name = "dummy_pcm_drv",
},
};
module_platform_driver(dummy_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Dummy PCM create/free example");
编译与加载
# 内核树外编译(需内核头文件与build目录)
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载模块
sudo insmod dummy_pcm.ko
# 确认PCM设备已创建
cat /proc/asound/cards
ls /dev/snd/
验证方法
- 创建验证:加载模块后,执行
cat /proc/asound/cards应看到名为"dummy"的声卡;ls /dev/snd/应出现pcmC0D0p设备节点(C0=card0,D0=device0,p=playback)。 - 销毁验证:执行
sudo rmmod dummy_pcm后,再次查看/proc/asound/cards与/dev/snd/,确认声卡与PCM设备节点均已消失。 - 错误路径验证:若
snd_pcm_new失败(如内存不足),代码会调用snd_card_free回滚,不会泄漏声卡资源。
调用时机要点
snd_pcm_new()必须在snd_card_new()之后调用,因为PCM实例挂载在声卡上。snd_pcm_free()必须在snd_card_free()之前调用,否则声卡释放时会尝试访问已释放的PCM,导致内核崩溃。- 若PCM已通过
snd_card_register()注册,直接调用snd_pcm_free()即可——该函数内部会先注销设备再释放内存,无需手动调用snd_device_free()。 - 在
probe失败路径中,若snd_pcm_new成功但后续snd_card_register失败,需手动调用snd_pcm_free()再snd_card_free(),避免资源泄漏。
常见问题
- 重复释放:
snd_pcm_free()内部会从声卡设备链表中移除PCM,重复调用会导致空指针解引用。建议释放后将指针置NULL。 - substream数量:
snd_pcm_new第4、5参数分别指定播放和捕获方向的substream数量,本案例仅播放方向(8个),捕获方向为0。 - 设备节点编号:PCM设备编号从0开始,由声卡内PCM实例的创建顺序决定,与
snd_pcm_new的device参数(本案例为0)对应。