kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Linux内核的加密函数

http://bbs.chinaunix.net/thread-1984676-1-1.html

Linux内核支持很多加密算法,包括对称加密算法,如AES;摘要算法,如sha1,md5;压缩算法,如deflate。不过内核好像不支持非对称加密算法。这些算法作为加密函数框架的最底层,提供加密和解密的实际操作。这些函数可以在内核crypto文件夹下,相应的文件中找到。不过内核模块不能直接调用这些函数,因为它们并没有export。内核提供一个统一的框架,来管理这些算法。加密算法通过crypto_register_alg()和crypto_unregister_alg()注册。

内核将加密算法分为三类,1)cipher,2)compress,3)digest。加密函数框架中有相应的API封装,提供给模块调用。

对于使用这些加密函数,首先通过crypto_alloc_tfm()来分配一个加密函数对象的实例。初始化这些实例,然后就可以通过框架提供的API对数据进行加密和解密。完成以后,必须通过crypto_free_tfm()撤销实例。

下面是几个代码,或许能够够对内核的加密框架有更直观的了解:

1 digest算法(sha1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/gfp.h>
#include <linux/err.h>
#include <linux/syscalls.h>
#include <linux/slab.h>

struct crypto_tfm *tfm;
struct scatterlist sg[1];
char * code1 = "2ew34123132513451345";
char * code2 = "234123132513451345";

char *do_digest(char * code) {
	char *result;
	int code_len = strlen(code);

	tfm = crypto_alloc_tfm("sha1",0);
	if(IS_ERR(tfm))
		return 0;
	sg_init_one(sg,code,code_len);

	crypto_digest_init(tfm);
	crypto_digest_update(tfm,sg,1);
	result = (char *)kmalloc(sizeof(char)*50,GFP_KERNEL);
	if(result == NULL) {
		crypto_free_tfm(tfm);
		return 0;
	}
	memset(result,0,sizeof(char)*50);
	crypto_digest_final(tfm,result);
	crypto_free_tfm(tfm);
	return result;
}

static int __init test_init(void)
{
	char *result1,*result2;
	result1 = do_digest(code1);
	if(!result1)
		goto failed2;
	result2 = do_digest(code2);
	if(!result2)
		goto failed1;

	if(memcmp(result1,result2,50) != 0)
		printk("<1>code1 != code2\n");
	else
		printk("<1>code1 == code2\n");
	kfree(result2);
failed1:
	kfree(result1);
failed2:
	return 0;
}

static void __exit test_exit(void)
{

}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

2 compress算法(deflate)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/gfp.h>
#include <linux/err.h>
#include <linux/syscalls.h>
#include <linux/slab.h>

struct crypto_tfm *tfm;
char * code = "Hello everyone, I'm richardhesidu from chinaunix.net !";


static inline void hexdump(unsigned char *buf,unsigned int len) {
	while(len--)
		printk("0x%02x,",*buf++);
	printk("\n");
}

static int __init test_init(void) {
	int ret,result_len,temp_len;
	char result[512];
	char temp[512];

	printk("<1>%s\n",code); 
 
	/* Allocate transform for deflate */
			
	tfm = crypto_alloc_tfm("deflate",0);
	if(IS_ERR(tfm)) {
		printk("<1>failed to load transform for deflate !\n");
		return 0;
	}

	memset(result,0,sizeof(result));

	temp_len = 512;
	ret = crypto_comp_compress(tfm,code,strlen(code),temp,&temp_len);
	if(ret) {
		printk("<1>failed to compress !\n");
		return 0;
	}

	hexdump(temp,strlen(temp));

	memset(result,0,sizeof(result));

	result_len = 512;
	ret = crypto_comp_decompress(tfm,temp,strlen(temp),result,&result_len);
	if(ret) {
		printk("<1>failed to decompress !\n");
		return 0;
	}

	printk("<1>%s\n",result);

	if(memcmp(code,result,strlen(code)) != 0)
		printk("<1>decompressed was not successful\n");
	else
		printk("<1>decompressed was successful\n");

	crypto_free_tfm(tfm);
	return 0;
}

static void __exit test_exit(void)
{

}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

3 cipher算法(aes)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/gfp.h>
#include <linux/err.h>
#include <linux/syscalls.h>
#include <linux/slab.h>
#include <linux/highmem.h>

struct crypto_tfm *tfm;
#if 1
char *code = "Hello everyone,I'm Richardhesidu"
		"Hello everyone,I'm Richardhesidu"
		"Hello everyone,I'm Richardhesidu";

char *key = "00112233445566778899aabbccddeeff";
#endif

#if 0
char code[] = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,
		0xbb,0xcc,0xdd,0xee,0xff};
char key[] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,
		0x0b,0x0c,0x0d,0x0e,0x0f};
#endif

static inline void hexdump(unsigned char *buf,unsigned int len) {
	while(len--)
		printk("%02x",*buf++);
	printk("\n");
}

static int __init test_init(void) {
	int ret,templen,keylen,codelen;
	struct scatterlist sg[1];
	char *result;
	char *temp;

	keylen = 16;
	codelen = strlen(code)/2;
#if 0
	printk("<1>%s, codelen=%d\n",code,strlen(code));
	printk("<1>%s, keylen=%d\n",key,strlen(key)); 
#endif 
	/* Allocate transform for AES ECB mode */
			
	tfm = crypto_alloc_tfm("aes",CRYPTO_TFM_MODE_ECB);
	if(IS_ERR(tfm)) {
		printk("<1>failed to load transform for aes ECB mode !\n");
		return 0;
	}

	ret = crypto_cipher_setkey(tfm,key,keylen);
	if(ret) {
		printk("<1>failed to setkey \n");
		goto failed1;
	}

	sg_init_one(sg,code,codelen);
		
	/* start encrypt */

	ret = crypto_cipher_encrypt(tfm,sg,sg,codelen);
	if(ret) {
		printk("<1>encrypt failed \n");
		goto failed1;
	}

	temp = kmap(sg[0].page) + sg[0].offset;

	hexdump(temp,sg[0].length);

	/* start dencrypt */
	templen = strlen(temp)/2;
	sg_init_one(sg,temp,templen);
	ret = crypto_cipher_decrypt(tfm,sg,sg,templen);
	if(ret) {
		printk("<1>dencrypt failed \n");
		goto failed1;
	}

	result = kmap(sg[0].page) + sg[0].offset;
	printk("<1>%s\n",result);
//    hexdump(result,sg[0].length);


#if 0
	if(memcmp(code,result,strlen(code)) != 0)
		printk("<1>dencrpt was not successful\n");
	else
		printk("<1>dencrypt was successful\n");
#endif
failed1:
	crypto_free_tfm(tfm);
	return 0;
}

static void __exit test_exit(void)
{

}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

截获alsa-pcm音频

http://blog.csdn.net/killmice/article/details/51777205

https://www.linuxidc.com/Linux/2014-11/109948.htm

http://www.ituring.com.cn/article/201363

avconv 命令录制

arecord -l

avconv -f alsa -i hw:0 output.wav


截获 alsa 框架中 pcm 缓冲区

sound/core/pcm_native.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const struct file_operations snd_pcm_f_ops[2] = {
	{
		.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,
	},   
	{
		.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_playback_ioctl 时 cmd=SNDRV_PCM_IOCTL_HWSYNC 时 copy 出 runtime->dma_area 对应数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct snd_pcm_substream *substream = (struct snd_pcm_substream *)regs->si;
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_pcm_mmap_status *status = runtime->status;
struct snd_pcm_mmap_control *control = runtime->control;

if (substream_now == substream) {
	unsigned int i, bytes = runtime->dma_bytes;
	unsigned char *ch = runtime->dma_area;

	/*
	printk("ioctl1: size=%d hw_ptr=%d appl_ptr=%d avail_min=%d audio_tstamp=%d\n",
		(int)runtime->dma_bytes, (int)status->hw_ptr, (int)control->appl_ptr,
		(int)control->avail_min, (int)status->audio_tstamp.tv_sec);
	*/

	if (status->hw_ptr > hw_ptr) {
		hw_ptr = status->hw_ptr;
		hw_ptr_err ++; 
	}

	for (i = hw_ptr * 4; before(i, control->appl_ptr * 4) && len < LEN; i ++, len ++)
		dest[len] = ch[i % bytes];

	hw_ptr = control->appl_ptr;
}

Alsa之pcm

http://www.alivepea.me/kernel/alsa-pcm/

pcm用来描述alsa中数字音频流。Alsa音频的播放/录制就是通过pcm来实现 的。

名词解释

声音是连续模拟量,计算机将它离散化之后用数字表示,就有了以下几个名词术语。

1
2
3
4
5
Frame. 帧是音频流中最小的单位,一段音频数据就是由苦干帧组成的。
Channel. 通道表示每帧数据中包含的通道数。单声道音频Mono含有 1个通道,立体声Stereo音频通常为2个通道。
Bit Depth. 位深,也叫采样精度,计算机对每个通道采样量化时数字比特位数,通常有16/24/32位。
Frames Per Second. 采样率表示每秒的采样帧数。常用的采样率如8KHz的人声, 44.1KHz的mp3音乐, 96Khz的蓝光音频。
Bits Per Second. 比特率表示每秒的比特数。

数据结构

snd_pcm结构用于表征一个PCM类型的snd_device.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct snd_pcm {
	struct snd_card *card; /* 指向所属的card设备 */
	int device; /* device number */
	struct snd_pcm_str streams[2]; /* 播放和录制两个数据流 */
	wait_queue_head_t open_wait; /* 打开pcm设备时等待打开一个可获得的substream */
}

struct snd_pcm_str {
	int stream;               /* stream (direction) */
	struct snd_pcm *pcm; /* 指向所属的pcm设备 */
	/* -- substreams -- */
	unsigned int substream_count; /* 个数 */
	unsigned int substream_opened; /* 在使用的个数 */
	struct snd_pcm_substream *substream; /* 指向substream单链表 */
}

文件"/proc/asound/cardX/pcmXp/info"可以查看pcm的信息。一个pcm设备包含播 放/录制两个流,每个流有若干个substream.一个substream只能被一个进程占用。 snd_pcm_substream才是真正实现音频的播放或录制的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct snd_pcm_substream {
	struct snd_pcm *pcm;
	struct snd_pcm_str *pstr;
	void *private_data;       /* copied from pcm->private_data */
	int number;
	char name[32];            /* substream name */
	int stream;           /* stream (direction) */ /* 录制/播放 */
	struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
	size_t buffer_bytes_max;  /* limit ring buffer size */
	struct snd_dma_buffer dma_buffer;
	unsigned int dma_buf_id;
	size_t dma_max;
	/* -- hardware operations -- */
	const struct snd_pcm_ops *ops;
	/* -- runtime information -- */
	struct snd_pcm_runtime *runtime;
        /* -- timer section -- */
	struct snd_timer *timer;      /* timer */
	unsigned timer_running: 1;    /* time is running */
	/* -- next substream -- */
	struct snd_pcm_substream *next;
	/* -- linked substreams -- */
	struct list_head link_list;   /* linked list member */
	struct snd_pcm_group self_group;  /* fake group for non linked substream (with substream lock inside) */
	struct snd_pcm_group *group;      /* pointer to current group */
	/* -- assigned files -- */
	void *file; /* 指向 pcm_file, 不知道有什么用? */
	int ref_count; /* 引用计数,打开 O_APPEND 时有用 */
	atomic_t mmap_count;  /* mmap 的引用计数 */
	unsigned int f_flags; /* pcm 打开的文件标记 */
	void (*pcm_release)(struct snd_pcm_substream *);
	struct pid *pid; /* 所在进程的pid,有多个substream时用于选择使用哪个 */
	/* misc flags */
	unsigned int hw_opened: 1; /* 若已打开,在释放substream时需要调用close() */
};

文件"/proc/asound/cardX/pcmXp/subX/info"可以查看这个substream的信息。这 个结构里两个最重要的成员是runtime和ops.

snd_pcm_ops是substream的操作方法集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct snd_pcm_ops {
	int (*open)(struct snd_pcm_substream *substream); /* 必须实现 */
	int (*close)(struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_pcm_substream * substream,
		     unsigned int cmd, void *arg); /* 用于实现几个特定的IOCTL1_{RESET,INFO,CHANNEL_INFO,GSTATE,FIFO_SIZE} */
	int (*hw_params)(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params); /* 用于设定pcm参数,如采样率/位深... */
	int (*hw_free)(struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_pcm_substream *substream); /* 读写数据前的准备 */
	int (*trigger)(struct snd_pcm_substream *substream, int cmd); /* 触发硬件对数据的启动/停止 */
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); /* 查询当前的硬件指针 */
	int (*wall_clock)(struct snd_pcm_substream *substream,
			  struct timespec *audio_ts); /* 通过hw获得audio_tstamp */
	int (*copy)(struct snd_pcm_substream *substream, int channel,
		    snd_pcm_uframes_t pos,
		    void __user *buf, snd_pcm_uframes_t count); /* 除dma外的hw自身实现的数据传输方法 */
	int (*silence)(struct snd_pcm_substream *substream, int channel,
		       snd_pcm_uframes_t pos, snd_pcm_uframes_t count); /* hw静音数据的填充方法 */
	struct page *(*page)(struct snd_pcm_substream *substream,
			     unsigned long offset); /* 硬件分配缓冲区的方法 */
	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma); /* */
	int (*ack)(struct snd_pcm_substream *substream); /* 通知硬件写了一次数据 */
};

这些操作方法集由各种声卡如PCI,USB,SOC等子模块来实现。

snd_pcm_runtime用于表示substream运行时状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct snd_pcm_runtime {
	/* -- Status -- */ /* */

	/* -- HW params -- */ /* 当前流的数据格式 */

	/* -- SW params -- */ /* 用户配置的参数如pcm_config */

	/* -- mmap -- */
	struct snd_pcm_mmap_status *status; /* 当前硬件指针位置及其状态 */
	struct snd_pcm_mmap_control *control; /* 当前的应用指针及其状态 */

	/* -- locking / scheduling -- */ /* 用于通知如数据空闲/溢出等事件 */

	/* -- private section -- */

	/* -- hardware description -- */ /* 硬件支持的参数及参数之间的约束条件 */

	/* -- interrupt callbacks -- */ /* HW一次中断传输完毕时的回调,似乎没有哪个模块用到它? */
	void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
	void (*transfer_ack_end)(struct snd_pcm_substream *substream);

	/* -- timer -- */

	/* -- DMA -- */

	struct snd_dma_buffer *dma_buffer_p;  /* allocated buffer */
}

这是相当大的一个结构体,自带的注释很明晰,就不贴它的成员了。它反映了一个 substream运行时的状态及实时信息。文件"/proc/asound/*/subX/“可以得到这个 结构的大部分信息。

PCM的状态转换

下图是PCM的状态的转换图。

除XRUN状态之后,其它的状态大多都由用户空间的ioctl()显式的切换。 以TinyAlsa的播放音频流程为例。 pcm_open()的对应的流程就是:

open(pcm)后绑定一个substream,处于OPEN状态

ioctl(SNDRV_PCM_IOCTL_SW_PARAMS)设定参数pcm_config.配置 runtime 的 sw_para.切换到SETUP状态

Tinyalsa的pcm_wirte()流程:

ioctl(SNDRV_PCM_IOCTL_PREPARE)后,substream切换到PREPARE状态。

ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES)后,substream切换到RUNNING状态。

TinyAlsa的pcm_mmap_write()流程:

ioctl(SNDRV_PCM_IOCTL_PREPARE)后,substream切换到PREPARE状态。

ioctl(SNDRV_PCM_IOCTL_START)后,substream切换到RUNNING状态。

TinyAlsa pcm_close流程:

ioctl(SNDRV_PCM_IOCTL_DROP)后,切换回SETUP状态。

close()之后,释放这个设备。

XRUN状态又分有两种,在播放时,用户空间没及时写数据导致缓冲区空了,硬件没有 可用数据播放导致UNDERRUN;录制时,用户空间没有及时读取数据导致缓冲区满后溢出, 硬件录制的数据没有空闲缓冲可写导致OVERRUN.

缓冲区的管理

音频的缓冲区是典型的只有一个读者和一个写者的FIFO结构。 下图是ALSA中FIFO缓冲区的示意图。

上图以播放时的缓冲管理为例,runtime->boundary一般都是较大的数,ALSA中默认接近 LONG_MAX/2.这样FIFO的出队入队指针不是真实的缓冲区的地址偏移,经过转换才得到 物理缓冲的偏移。这样做的好处是简化了缓冲区的管理,只有在更新hw指针的时候才需 要换算到hw_ofs.

当用户空间由于系统繁忙等原因,导致hw_ptr>appl_ptr时,缓冲区已空,内核这里有两种方案:

停止DMA传输,进入XRUN状态。这是内核默认的处理方法。

继续播放缓冲区的重复的音频数据或静音数据。

用户空间配置stop_threshold可选择方案1或方案2,配置silence_threshold选择继 续播放的原有的音频数据还是静意数据了。个人经验,偶尔的系统繁忙导致的这种状态, 重复播放原有的音频数据会显得更平滑,效果更好。

实现

pcm的代码让人难以理解的部分莫过于硬件指针的更新snd_pcm_update_hw_ptr0(),分 析见这里。它是将hw_ofs转换成FIFO中 hw_ptr的过程,同时处理环形缓冲区的回绕,没有中断,中断丢失等情况。

还有一处就是处理根据硬件参数的约束条件得到参数的代码 snd_pcm_hw_refine(substream, params). 留待以后分析吧。

调试

“sound/core/info.c"是alsa为proc实现的接口。这也是用户空间来调试内核alsa 最主要的方法了。打开内核配置选项 CONFIG_SND_VERBOSE_PROCFS/CONFIG_SND_PCM_XRUN_DEBUG,可看到以下的目录树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/proc/asound/
|-- card0
|   |-- id  声卡名
|   |-- pcm0c
|   |   |-- info pcm设备信息
|   |   |-- sub0
|   |   |   |-- hw_params 硬件配置参数
|   |   |   |-- info substream设备信息
|   |   |   |-- status 实时的hw_ptr/appl_ptr
|   |   |   `-- sw_params  软件配置参数
|   |   `-- xrun_debug  控制内核alsa的调试日志输出
|   `-- pcm0p
|-- cards 内核拥有的声卡
|-- devices 内核所有的snd_device设备
|-- pcm 所有的pcm设备
`-- version alsa的版本号

在ALSA播放/录制异常时,若打开xrun_debug,内核日志会实时打印更多有用的信息, 往"/proc/asound/card0/pcm0p/xrun_debug"写入相应的掩码就好了。

1
2
3
4
5
6
7
#define XRUN_DEBUG_BASIC (1<<0)
#define XRUN_DEBUG_STACK  (1<<1)    /* dump also stack */
#define XRUN_DEBUG_JIFFIESCHECK   (1<<2)    /* do jiffies check */
#define XRUN_DEBUG_PERIODUPDATE   (1<<3)    /* full period update info */
#define XRUN_DEBUG_HWPTRUPDATE    (1<<4)    /* full hwptr update info */
#define XRUN_DEBUG_LOG        (1<<5)    /* show last 10 positions on err */
#define XRUN_DEBUG_LOGONCE    (1<<6)    /* do above only once */

相当冗长的一篇总结。与其它内核模块比起来,这部分代码似乎显得更“晦涩”,原因 之一可能就是音频流是实时的数据,而内核本身不是实时的系统,软件上不能很好的保 证hw_ptr和appl_ptr的同步。