2. PCM设备创建与销毁
任务目标
在Linux内核驱动中,通过ALSA框架动态创建PCM设备(playback方向),实现基本的open/release回调,并在用户空间验证设备节点可被正常打开。本案例聚焦于PCM设备生命周期管理的最小可复现实现。
环境依赖
- Linux内核源码树(本案例基于5.15版本验证)
- 内核编译工具链(gcc、make、libncurses-dev等)
- 开发板或虚拟机,支持加载内核模块
- 用户空间工具:
aplay(alsa-utils)或直接使用cat验证设备节点存在性
核心实现
以下代码创建一个ALSA声卡设备,并在其上注册一个PCM playback设备。模块加载时动态创建,卸载时销毁。PCM的open/release回调仅维护一个打开计数器,用于验证用户空间访问路径。
// pcm_create_destroy.c
#include <linux/module.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#define DRIVER_NAME "pcm_demo"
static struct snd_card *card;
static struct snd_pcm *pcm;
static int open_count;
static int pcm_demo_open(struct snd_pcm_substream *substream)
{
open_count++;
pr_info("pcm_demo: open called, count=%d\n", open_count);
return 0;
}
static int pcm_demo_close(struct snd_pcm_substream *substream)
{
open_count--;
pr_info("pcm_demo: close called, count=%d\n", open_count);
return 0;
}
static const struct snd_pcm_ops pcm_demo_ops = {
.open = pcm_demo_open,
.close = pcm_demo_close,
};
static int __init pcm_demo_init(void)
{
int ret;
/* 第一步:创建声卡 */
ret = snd_card_new(NULL, -1, NULL, THIS_MODULE, 0, &card);
if (ret < 0) {
pr_err("pcm_demo: snd_card_new failed: %d\n", ret);
return ret;
}
/* 第二步:在声卡上创建PCM设备 */
ret = snd_pcm_new(card, "pcm_demo_pcm", 0, 1, 0, &pcm);
if (ret < 0) {
pr_err("pcm_demo: snd_pcm_new failed: %d\n", ret);
goto fail_pcm;
}
/* 第三步:绑定PCM操作集 */
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_demo_ops);
/* 第四步:注册声卡,设备节点出现在/dev/snd/ */
ret = snd_card_register(card);
if (ret < 0) {
pr_err("pcm_demo: snd_card_register failed: %d\n", ret);
goto fail_register;
}
pr_info("pcm_demo: initialized, card#%d, PCM device 0-0\n", card->number);
return 0;
fail_register:
snd_pcm_free(pcm);
fail_pcm:
snd_card_free(card);
return ret;
}
static void __exit pcm_demo_exit(void)
{
/* 销毁顺序:先释放PCM,再释放声卡 */
if (pcm)
snd_pcm_free(pcm);
if (card)
snd_card_free(card);
pr_info("pcm_demo: exited\n");
}
module_init(pcm_demo_init);
module_exit(pcm_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALSA Demo");
MODULE_DESCRIPTION("PCM device create/destroy demo");
Makefile
obj-m := pcm_create_destroy.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
编译与加载
- 将上述两个文件放入同一目录,执行
make编译,生成pcm_create_destroy.ko。 - 加载模块:
sudo insmod pcm_create_destroy.ko - 检查内核日志确认初始化成功:
dmesg | tail,应看到pcm_demo: initialized, card#N, PCM device 0-0。
用户空间验证
模块加载后,ALSA框架会在/dev/snd/下生成设备节点。执行以下命令验证PCM设备可被打开:
# 查看生成的设备节点(card编号以实际dmesg输出为准)
ls -l /dev/snd/
# 使用aplay列出PCM设备(应能看到pcm_demo设备)
aplay -l
# 直接打开设备节点验证open回调被触发
cat /dev/snd/pcmC0D0p &
sleep 0.1
dmesg | tail -5 # 应看到 "pcm_demo: open called, count=1"
kill %1
dmesg | tail -5 # 应看到 "pcm_demo: close called, count=0"
通过判定标准:
aplay -l能列出名为pcm_demo的playback设备;- 打开
/dev/snd/pcmC0D0p时内核日志出现open called,关闭后出现close called; - 卸载模块
sudo rmmod pcm_create_destroy后,设备节点消失,日志显示pcm_demo: exited。
常见问题
- 加载失败提示
No such device:确认内核已启用CONFIG_SND和CONFIG_SND_PCM,且声卡驱动框架可用。 - 设备节点未生成:检查
dmesg中是否有注册错误,确认snd_card_register成功返回。 - 打开设备节点时阻塞:由于未实现hw_params/prepare等回调,应用层调用
snd_pcm_hw_params会失败,但open()系统调用本身应正常返回。本案例仅验证open/release路径,不涉及音频数据传输。