Ted's Blog

Happy coding

Wget用法、参数解释的比较好的一个文章

wget是一个从网络上自动下载文件的自由工具。它支持HTTP,HTTPS和FTP协议,可以使用HTTP代理.

所谓的自动下载是指,wget可以在用户退出系统的之后在后台执行。这意味这你可以登录系统,启动一个wget下载任务,然后退出系统,wget将在后台执行直到任务完成,相对于其它大部分浏览器在下载大量数据时需要用户一直的参与,这省去了极大的麻烦。
wget 可以跟踪HTML页面上的链接依次下载来创建远程服务器的本地版本,完全重建原始站点的目录结构。这又常被称作"递归下载"。在递归下载的时候,wget 遵循Robot Exclusion标准(/robots.txt). wget可以在下载的同时,将链接转换成指向本地文件,以方便离线浏览。
wget 非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性.如果是由于网络的原因下载失败,wget会不断的尝试,直到整个文件下载完毕。如果是服务 器打断下载过程,它会再次联到服务器上从停止的地方继续下载。这对从那些限定了链接时间的服务器上下载大文件非常有用。
wget的常见用法
wget虽然功能强大,但是使用起来还是比较简单的,
基本的语法是:wget [参数列表] "URL" 用""引起来可以避免因URL中有特殊字符造成的下载出错。
下面就结合具体的例子来说明一下wget的用法。
    1、下载整个http或者ftp站点。
    wget http://place.your.url/here
    这个命令可以将http://place.your.url/here 首页下载下来。使用-x会强制建立服务器上一模一样的目录,如果使用-nd参数,那么服务器上下载的所有内容都会加到本地当前目录。

    wget -r http://place.your.url/here
    这个命令会按照递归的方法,下载服务器上所有的目录和文件,实质就是下载整个网站。这个命令一定要小心使用,因为在下载的时候,被下载网站指向的所有地址 同样会被下载,因此,如果这个网站引用了其他网站,那么被引用的网站也会被下载下来!基于这个原因,这个参数不常用。可以用-l number参数来指定下载的层次。例如只下载两层,那么使用-l 2。

    要是您想制作镜像站点,那么可以使用-m参数,例如:wget -m http://place.your.url/here
    这时wget会自动判断合适的参数来制作镜像站点。此时,wget会登录到服务器上,读入robots.txt并按robots.txt的规定来执行。

    2、断点续传。
    当文件特别大或者网络特别慢的时候,往往一个文件还没有下载完,连接就已经被切断,此时就需要断点续传。wget的断点续传是自动的,只需要使用-c参数,例如:
    wget -c http://the.url.of/incomplete/file
    使用断点续传要求服务器支持断点续传。-t参数表示重试次数,例如需要重试100次,那么就写-t 100,如果设成-t 0,那么表示无穷次重试,直到连接成功。-T参数表示超时等待时间,例如-T 120,表示等待120秒连接不上就算超时。

    3、批量下载。
    如果有多个文件需要下载,那么可以生成一个文件,把每个文件的URL写一行,例如生成文件download.txt,然后用命令:wget -i download.txt
这样就会把download.txt里面列出的每个URL都下载下来。(如果列的是文件就下载文件,如果列的是网站,那么下载首页)

    4、选择性的下载。
    可以指定让wget只下载一类文件,或者不下载什么文件。例如:
    wget -m --reject=gif http://target.web.site/subdirectory
    表示下载http://target.web.site/subdirectory,但是忽略gif文件。--accept=LIST 可以接受的文件类型,--reject=LIST拒绝接受的文件类型。

    5、密码和认证。
    wget只能处理利用用户名/密码方式限制访问的网站,可以利用两个参数:
    --http-user=USER设置HTTP用户
    --http-passwd=PASS设置HTTP密码
    对于需要证书做认证的网站,就只能利用其他下载工具了,例如curl。

    6、利用代理服务器进行下载。
    如果用户的网络需要经过代理服务器,那么可以让wget通过代理服务器进行文件的下载。此时需要在当前用户的目录下创建一个.wgetrc文件。文件中可以设置代理服务器:
    http-proxy = 111.111.111.111:8080
    ftp-proxy = 111.111.111.111:8080
    分别表示http的代理服务器和ftp的代理服务器。如果代理服务器需要密码则使用:
    --proxy-user=USER设置代理用户
    --proxy-passwd=PASS设置代理密码
    这两个参数。
    使用参数--proxy=on/off 使用或者关闭代理。
    wget还有很多有用的功能,需要用户去挖掘。




wget的使用格式
Usage: wget [OPTION]... [URL]...
* 用wget做站点镜像:
wget -r -p -np -k http://dsec.pku.edu.cn/~usr_name/
# 或者
wget -m http://dsec.pku.edu.cn/~usr_name/
* 在不稳定的网络上下载一个部分下载的文件,以及在空闲时段下载
wget -t 0 -w 31 -c http://dsec.pku.edu.cn/BBC.avi -o down.log &
# 或者从filelist读入要下载的文件列表
wget -t 0 -w 31 -c -B ftp://dsec.pku.edu.cn/linuxsoft -i filelist.txt -o down.log &
上面的代码还可以用来在网络比较空闲的时段进行下载。我的用法是:在mozilla中将不方便当时下载的URL链接拷贝到内存中然后粘贴到文件filelist.txt中,在晚上要出去系统前执行上面代码的第二条。
* 使用代理下载
wget -Y on -p -k https://sourceforge.net/projects/wvware/
代理可以在环境变量或wgetrc文件中设定
# 在环境变量中设定代理
export PROXY=http://211.90.168.94:8080/
# 在~/.wgetrc中设定代理
http_proxy = http://proxy.yoyodyne.com:18023/
ftp_proxy = http://proxy.yoyodyne.com:18023/


wget各种选项分类列表
* 启动
  -V,  --version           显示wget的版本后退出
  -h,  --help              打印语法帮助
  -b,  --background        启动后转入后台执行
  -e,  --execute=COMMAND   执行`.wgetrc'格式的命令,wgetrc格式参见/etc/wgetrc或~/.wgetrc
* 记录和输入文件
  -o,  --output-file=FILE     把记录写到FILE文件中
  -a,  --append-output=FILE   把记录追加到FILE文件中
  -d,  --debug                打印调试输出
  -q,  --quiet                安静模式(没有输出)
  -v,  --verbose              冗长模式(这是缺省设置)
  -nv, --non-verbose          关掉冗长模式,但不是安静模式
  -i,  --input-file=FILE      下载在FILE文件中出现的URLs
  -F,  --force-html           把输入文件当作HTML格式文件对待
  -B,  --base=URL             将URL作为在-F -i参数指定的文件中出现的相对链接的前缀
       --sslcertfile=FILE     可选客户端证书
       --sslcertkey=KEYFILE   可选客户端证书的KEYFILE
       --egd-file=FILE        指定EGD socket的文件名
* 下载
       --bind-address=ADDRESS   指定本地使用地址(主机名或IP,当本地有多个IP或名字时使用)
  -t,  --tries=NUMBER           设定最大尝试链接次数(0 表示无限制).
  -O   --output-document=FILE   把文档写到FILE文件中
  -nc, --no-clobber             不要覆盖存在的文件或使用.#前缀
  -c,  --continue               接着下载没下载完的文件
       --progress=TYPE          设定进程条标记
  -N,  --timestamping           不要重新下载文件除非比本地文件新
  -S,  --server-response        打印服务器的回应
       --spider                 不下载任何东西
  -T,  --timeout=SECONDS        设定响应超时的秒数
  -w,  --wait=SECONDS           两次尝试之间间隔SECONDS秒
       --waitretry=SECONDS      在重新链接之间等待1...SECONDS秒
       --random-wait            在下载之间等待0...2*WAIT秒
  -Y,  --proxy=on/off           打开或关闭代理
  -Q,  --quota=NUMBER           设置下载的容量限制
       --limit-rate=RATE        限定下载输率
* 目录
  -nd  --no-directories            不创建目录
  -x,  --force-directories         强制创建目录
  -nH, --no-host-directories       不创建主机目录
  -P,  --directory-prefix=PREFIX   将文件保存到目录 PREFIX/...
       --cut-dirs=NUMBER           忽略 NUMBER层远程目录
* HTTP 选项
       --http-user=USER      设定HTTP用户名为 USER.
       --http-passwd=PASS    设定http密码为 PASS.
  -C,  --cache=on/off        允许/不允许服务器端的数据缓存 (一般情况下允许).
  -E,  --html-extension      将所有text/html文档以.html扩展名保存
       --ignore-length       忽略 `Content-Length'头域
       --header=STRING       在headers中插入字符串 STRING
       --proxy-user=USER     设定代理的用户名为 USER
       --proxy-passwd=PASS   设定代理的密码为 PASS
       --referer=URL         在HTTP请求中包含 `Referer: URL'头
  -s,  --save-headers        保存HTTP头到文件
  -U,  --user-agent=AGENT    设定代理的名称为 AGENT而不是 Wget/VERSION.
       --no-http-keep-alive  关闭 HTTP活动链接 (永远链接).
       --cookies=off         不使用 cookies.
       --load-cookies=FILE   在开始会话前从文件 FILE中加载cookie
       --save-cookies=FILE   在会话结束后将 cookies保存到 FILE文件中
* FTP 选项
  -nr, --dont-remove-listing   不移走 `.listing'文件
  -g,  --glob=on/off           打开或关闭文件名的 globbing机制
       --passive-ftp           使用被动传输模式 (缺省值).
       --active-ftp            使用主动传输模式
       --retr-symlinks         在递归的时候,将链接指向文件(而不是目录)
* 递归下载
  -r,  --recursive          递归下载--慎用!
  -l,  --level=NUMBER       最大递归深度 (inf 或 0 代表无穷).
       --delete-after       在现在完毕后局部删除文件
  -k,  --convert-links      转换非相对链接为相对链接
  -K,  --backup-converted   在转换文件X之前,将之备份为 X.orig
  -m,  --mirror             等价于 -r -N -l inf -nr.
  -p,  --page-requisites    下载显示HTML文件的所有图片
* 递归下载中的包含和不包含(accept/reject)
  -A,  --accept=LIST                分号分隔的被接受扩展名的列表
  -R,  --reject=LIST                分号分隔的不被接受的扩展名的列表
  -D,  --domains=LIST               分号分隔的被接受域的列表
       --exclude-domains=LIST       分号分隔的不被接受的域的列表
       --follow-ftp                 跟踪HTML文档中的FTP链接
       --follow-tags=LIST           分号分隔的被跟踪的HTML标签的列表
  -G,  --ignore-tags=LIST           分号分隔的被忽略的HTML标签的列表
  -H,  --span-hosts                 当递归时转到外部主机
  -L,  --relative                   仅仅跟踪相对链接
  -I,  --include-directories=LIST   允许目录的列表
  -X,  --exclude-directories=LIST   不被包含目录的列表
  -np, --no-parent                  不要追溯到父目录
问题
在递归下载的时候,遇到目录中有中文的时候,wget创建的本地目录名会用URL编码规则处理。如"天网防火墙"会被存为"%CC%EC%CD%F8%B7%C0%BB%F0%C7%BD",这造成阅读上的

linux SSH 的一些安全小技巧

一, 前言
  
关于 ssh 的好处, 相信不用我多说了吧?

简而言之, 之前的 rpc command 与 telnet 都全可用 ssh 代替.

比方如下的这些常见功能:

- 远程登录
ssh user@remote.machine
- 远程执行
ssh user@remote.machine 'command ...'
- 远程粗?
scp user@remote.machine:/remote/path /local/path
scp /local/path user@remote.machine:/remote/path
- X forward
ssh -X user@remote.machine
xcommand ...
- Tunnel / Portforward
ssh -L 1234:remote.machine:4321 user@remote.machine
ssh -R 1234:local.machine:4321 user@remote.machine
ssh -L 1234:other.machine:4321 user@remote.machine

至于详细的用法, 我这就不说了. 请读者自行研究吧.

我这里要说的, 是针对 ssh 服务为大家介绍一些安全技巧, 希望大家用得更安心些.

二, 实作

(实作以 RedHat 9 为范例)

1) 禁止 root 登录

# vi /etc/ssh/sshd_config
PermitRootLogin no

2) 废除密码登录, 强迫使用 RSA 验证(假设 ssh 账户为 user1 )

# vi /etc/ssh/sshd_config
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile   .ssh/authorized_keys
PasswordAuthentication no
# service sshd restart
# su - user1
$ mkdir ~/.ssh 2>/dev/null
$ chmod 700 ~/.ssh
$ touch ~/.ssh/authorized_keys
$ chmod 644 ~/.ssh/authorized_keys

--------------------------------------------------
转往 client 端:
$ ssh-keygen -t rsa
(按三下 enter 完成﹔不需设密码,除非您会用 ssh-agent 。)
$ scp ~/.ssh/id_rsa.pub user1@server.machine:id_rsa.pub
(若是 windows client, 可用 puttygen.exe 产生 public key,
然后复制到 server 端后修改之, 使其内容成为单一一行.)
---------------------------------------------------

回到 server 端:
$ cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
$ rm ~/id_rsa.pub
$ exit

3) 限制 su / sudo 名单:

# vi /etc/pam.d/su
auth required /lib/security/$ISA/pam_wheel.so use_uid
# visudo
%wheel ALL=(ALL)  ALL
# gpasswd -a user1 wheel

4) 限制 ssh 使用者名单

# vi /etc/pam.d/sshd
auth    required   pam_listfile.so item=user sense=allow file=/etc/ssh_users onerr=fail
# echo user1 >> /etc/ssh_users

5) 封锁 ssh 联机并改用 web 控管清单

# iptables -I INPUT -p tcp --dport 22 -j DROP
# mkdir /var/www/html/ssh_open
# cat > /var/www/html/ssh_open/.htaccess <<END
AuthName "ssh_open"
AuthUserFile /var/www/html/ssh_open/.htpasswd
AuthType basic
require valid-user
END
# htpasswd -c /var/www/html/ssh_open/.htpasswd user1

(最好还将 SSL 设起来, 或只限 https 联机更佳, 我这里略过 SSL 设定, 请读者自补.)

(如需控制联机来源, 那请再补 Allow/Deny 项目, 也请读者自补.)

# cat > /var/www/html/ssh_open/ssh_open.php <<END
<?
//Set dir path for ip list
$dir_path=".";

//Set filename for ip list
$ip_list="ssh_open.txt";

//Get client ip
$user_ip=$_SERVER['REMOTE_ADDR'];

//allow specifying ip if needed
if (@$_GET['myip']) {
$user_ip=$_GET['myip'];
}

//checking IP format
if ($user_ip==long2ip(ip2long($user_ip))) {

//Put client ip to a file
if(@!($file = fopen("$dir_path/$ip_list","w+")))
{
echo "Permission denied!!<br>";
echo "Pls Check your rights to dir $dir_path or file $ip_list";
}
else
{
fputs($file,"$user_ip");
fclose($file);
echo "client ip($user_ip) has put into $dir_path/$ip_list";
}
} else {
echo "Invalid IP format!!<br>ssh_open.txt was not changed.";
}
?>
END
# touch /var/www/html/ssh_open/ssh_open.txt
# chmod 640 /var/www/html/ssh_open/*
# chgrp apache /var/www/html/ssh_open/*
# chmod g+w /var/www/html/ssh_open/ssh_open.txt
# chmod o+t /var/www/html/ssh_open
# service httpd restart
# mkdir /etc/iptables
# cat > /etc/iptables/sshopen.sh <<END
#!/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin

list_dir=/var/www/html/ssh_open
list_file=$list_dir/ssh_open.txt
chain_name=ssh_rules
mail_to=root

# clear chain if exits, or create chain.
iptables -L -n | /bin/grep -q "^Chain $chain_name" && {
iptables -F $chain_name
true
} || {
iptables -N $chain_name
iptables -I INPUT -p tcp --dport 22 -j $chain_name
}

# clear chain when needed
[ "$1" = clear ] && {
iptables -F $chain_name
exit 0
}

# do nothing while list is empty
[ -s $list_file ] || exit 1

# add rule
iptables -A $chain_name -p tcp --dport 22 -s $(< $list_file) -j ACCEPT && echo "ssh opened to $(< $list_file) on $(date)" | mail -s "sshopen" $mail_to
END
# chmod +x /etc/iptables/sshopen.sh
# echo -e 'sshopen\t\t1234/tcp' >> /etc/services
# cat > /etc/xinetd.d/sshopen <<END
service sshopen
{
disable = no
socket_type   = stream
protocol    = tcp
wait      = no
user      = root
server     = /etc/iptables/sshopen.sh
}
# iptables -I INPUT -p tcp --dport 1234 -j ACCEPT
# cat > /etc/cron.d/sshopen <<END
*/5 * * * *   root  /etc/iptables/sshopen.sh clear
END

---------------------------
转往 client 端
在 browser URL 输入:
http://server.machine/ssh_open/ssh_open.php?myip=1.2.3.4
(若不指定 ?myip=1.2.3.4 则以 client 当时 IP 为准, 若没经 proxy 的话.)
如此, server 端的 ssh_open.txt 文件只有单一记录, 每次盖写.
接着:
$ telnet server.machine 1234
然后你有最多 5 分钟时间用 ssh 联机 server !
---------------------------

此步骤的基本构思如下:

5.1) 将 sshd 的 firewall 联机全部 block 掉.

5.2) 然后在 httpd 那设一个 directory, 可设 ssl+htpasswd+allow/deny control,

然后在目录内写一个 php 将 browser ip 记录于一份 .txt 文字文件里.

视你的转写能力, 你可自动抓取 browser 端的 IP, 也可让 browser 端传入参数来指定.

文字文件只有单一记录, 每次盖写.

5.3) 修改 /etc/services , 增加一个新项目(如 xxx), 并指定一个新 port(如 1234)

5.4) 再用 xinetd 监听该 port , 并启动令一只 script, 设定 iptables , 从 step2 的清单里取得 IP, 为之打开 ssh 联机.

5.5) 设 crontab 每数分中清理 iptables 关于 ssh 联机的规则. 这并不影响既有联机, 若逾时再连, 则重复上述.

6) 要是上一步骤没设定, 你或许会担心过多的人来 try 你的 ssh 服务的话:

# cat > /etc/iptables/sshblock.sh <<END
#!/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin

LOG_FILE=/var/log/secure
KEY_WORD="Illegal user"
KEY_WORD1="Failed password for root"
PERM_LIST=/etc/firewall/bad.list.perm
LIMIT=5
MAIL_TO=root
IPT_SAV="$(iptables-save)"
bad_list=$(egrep "$KEY_WORD" $LOG_FILE | awk '{print $NF}' | xargs)
bad_list1=$(egrep "$KEY_WORD1" $LOG_FILE | awk '{print $11}' | xargs)
bad_list="$bad_list $bad_list1"

for i in $(echo -e "${bad_list// /\n}" | sort -u)
do
hit=$(echo $bad_list | egrep -o "$i" | wc -l)
[ "$hit" -ge "$LIMIT" ] && {
echo "$IPT_SAV" | grep -q "$i .*-j DROP" || {
echo -e "\n$i was dropped on $(date)\n" | mail -s "DROP by ${0##*/}: $i" $MAIL_TO
iptables -I INPUT -s $i -j DROP
}
egrep -q "^$i$" $PERM_LIST || echo $i >> $PERM_LIST
}
done
END
# chmod +x /etc/firewall/sshblock.sh
# cat >> /etc/hosts.allow <<END
sshd: ALL: spawn ( /etc/firewall/sshblock.sh )& : ALLOW
END

这样, 那些乱 try SSH 的家伙, 顶多能试 5 次(LIMIT 可调整), 然后就给 BLOCK 掉了.
此外, 在 PERM_LIST 的 ip, 也可提供给 iptables 的初始 script , 来个永久性封闭:
for i in $(< $PERM_LIST)
do
/sbin/iptables -I INPUT -s $i -j DROP
done

7) 还有, 你想知道有哪些人对你做 full range port scan 的话:

# iptables -I INPUT -p tcp --dport 79 -j ACCEPT
cat > /etc/xinetd.d/finger <<END
service finger
{
socket_type   = stream
wait      = no
user      = nobody
server     = /usr/sbin/in.fingerd
disable     = no
}
END
# cat >> /etc/hosts.allow <<END
in.fingerd: ALL : spawn ( echo -e "\nWARNING %a was trying finger.\n$(date)" | mail -s "finger from %a" root ) & : DENY
END

这里, 我只是设为发信给 root.

事实上, 你可修改为起动 firewall 将 %a 这个传回值给 ban 掉也行.

不过, 对方要是有选择性的做 port scan , 没扫到 finger 的话, 那当然就没用了...

ssh for Linux 的配置

安全的ssh
SSH是一个用来替代TELNET、Rlogin以及Rsh的传统的远程登陆程序的工具, 主要是想解决口令在网上明文传输的问题。为了系统安全和用户 自身的权益, 推广SSH是必要的。SSH有两个不兼容的版本1.x,2.x!RedHat Linux 9将默认的远程管理服务设置成OpenSSH(一个ssh的替代产品)。不需要重新安装软件包!

一、配置openssh服务器    
1、ssh的配置文件是/etc/ssh/ssh_config,一般不要修改!  
2、启动服务器!  #ntsysv =>确认将sshd前面的勾已打上!  
3、手工启动OpenSSH:  
#service sshd start  
#service sshd restart(重新启动)  
4、停止服务器:  #service sshd stop    
二、使用OpenSSH客户端    
Redhat linux 9默认已安装了OpenSSH的客户端,客户端和服务器连接时,可以使
用两种验证方式:基于口令的验证方式和基于密匙的验证方式!  
1、基于口令的验证方式  
这种验证方式要求用户输入用户名称和密码!若没有指定用户名称和密码, 则默认使用当前在客户机上的用户名!    
例1:直接登陆  [root@wljs /]#ssh 210.45.160.17     则登陆用户名为客户机当前用户名!  
例2:指定用户名登陆   [root@wljs /]#ssh wwz@210.45.160.17  或:      [root@wljs /]#ssh –l wwz 210.45.160.17  上面过程结束后,系统将会提示你输入用户名和密码!    
2、基于密匙的验证方式  
使用密匙的验证方式,用户先需要为自己创建一对密匙:公匙和私匙。
   (公匙用在要登陆的服务器上)  
   OpenSSH公开密匙的密码体制有RSA、DSA!  
创建密匙:  
   例:[root@wljs /]#ssh-keygen –t rsa  
   回车后,要求输入使用密匙时的口令!这样便生成了公匙和私匙:
   放在用户主目录下的.ssh目录下,文件名:id_rsa.pub和id_rsa!必须
   将公匙复制到登陆的服务器的~/.ssh/目录下,并改名为:authorized_keys!
   然后,便可使用密匙方式登陆!  
   #ssh [–l username] ip地址或主机名    
三、OpenSSH上常用的命令    
1、不登陆远程系统使用命令  
#ssh 210.45.160.17 [命令] [参数]  
2、本地系统和远程系统间文件的传输  
#scp a.txt root@210.45.160.17:/b.txt  
#scp root@210.45.160.17:/b.txt /c.txt  
3、sftp命令  
Sftp 命令和ftp命令类似,它是OpenSSH提供的网络传输文件的小工具,
它更加安全,使用和ftp相似的命令:主要有如下几个:  
1、登陆  #ftp 210.45.160.17  
2、ftp 会话的打开与关闭  
   打开:open 210.45.160.27  
   关闭:close  
3、文件的传输  
   从ftp服务器上得到文件:  Get a.txt  
   向ftp上放文件  Put a.txt  
4、退出ftp  Bye  
5、其他  
   bell:每个命令执行完毕后计算机响铃一次  
   Cd ,ls 等一些常见命令也可以在ftp服务器目录中使用!

 

scp命令介绍

scp就是secure copy,是用来进行远程文件拷贝的.数据传输使用ssh1,并且和ssh1使用相同的认证方式,提供相同的安全保证.与rcp不同的是,scp会要求你输入密码如果需要的话.
最简单的应用如下:
scp 本地用户名@IP地址:文件名1 远程用户名@IP地址:文件名2
[本地用户名@IP地址:]可以不输入,可能需要输入远程用户名所对应的密码.
可能有用的几个参数:
-v 和大多数linux命令中的-v意思一样,用来显示进度.可以用来查看连接,认证,或是配置错误.
-C 使能压缩选项.
-P 选择端口.注意-p已经被rcp使用.
-4 强行使用IPV4地址.
-6 强行使用IPV6地址.
scp中很多参数都和ssh1有关,需要的话在看.

Filesystem Hierarchy Standard

Edited by

Rusty Russell

Daniel Quinlan

Christopher Yeoh

 

This standard consists of a set of requirements and guidelines for file and directory placement under UNIX-like operating systems. The guidelines are intended to support interoperability of applications, system administration tools, development tools, and scripts as well as greater uniformity of documentation for these systems.

 

 

All trademarks and copyrights are owned by their owners, unless specifically noted otherwise. Use of a term in this document should not be regarded as affecting the validity of any trademark or service mark.

Permission is granted to make and distribute verbatim copies of this standard provided the copyright and this permission notice are preserved on all copies.

Permission is granted to copy and distribute modified versions of this standard under the conditions for verbatim copying, provided also that the title page is labeled as modified including a reference to the original standard, provided that information on retrieving the original standard is included, and provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.

Permission is granted to copy and distribute translations of this standard into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the copyright holder.

 


Table of Contents
1. Introduction
Purpose
Conventions
2. The Filesystem
3. The Root Filesystem
Purpose
Requirements
Specific Options
/bin : Essential user command binaries (for use by all users)
Purpose
Requirements
Specific Options
/boot : Static files of the boot loader
Purpose
Specific Options
/dev : Device files
Purpose
Specific Options
/etc : Host-specific system configuration
Purpose
Requirements
Specific Options
/etc/opt : Configuration files for /opt
/etc/X11 : Configuration for the X Window System (optional)
/etc/sgml : Configuration files for SGML (optional)
/etc/xml : Configuration files for XML (optional)
/home : User home directories (optional)
Purpose
Requirements
/lib : Essential shared libraries and kernel modules
Purpose
Requirements
Specific Options
/lib<qual> : Alternate format essential shared libraries (optional)
Purpose
Requirements
/media : Mount point for removeable media
Purpose
Specific Options
/mnt : Mount point for a temporarily mounted filesystem
Purpose
/opt : Add-on application software packages
Purpose
Requirements
/root : Home directory for the root user (optional)
Purpose
/sbin : System binaries
Purpose
Requirements
Specific Options
/srv : Data for services provided by this system
Purpose
/tmp : Temporary files
Purpose
4. The /usr Hierarchy
Purpose
Requirements
Specific Options
/usr/X11R6 : X Window System, Version 11 Release 6 (optional)
Purpose
Specific Options
/usr/bin : Most user commands
Purpose
Specific Options
/usr/include : Directory for standard include files.
Purpose
Specific Options
/usr/lib : Libraries for programming and packages
Purpose
Specific Options
/usr/lib<qual> : Alternate format libraries (optional)
Purpose
/usr/local : Local hierarchy
/usr/local/share
/usr/sbin : Non-essential standard system binaries
Purpose
/usr/share : Architecture-independent data
Purpose
Requirements
Specific Options
/usr/share/dict : Word lists (optional)
/usr/share/man : Manual pages
/usr/share/misc : Miscellaneous architecture-independent data
/usr/share/sgml : SGML data (optional)
/usr/share/xml : XML data (optional)
/usr/src : Source code (optional)
Purpose
5. The /var Hierarchy
Purpose
Requirements
Specific Options
/var/account : Process accounting logs (optional)
Purpose
/var/cache : Application cache data
Purpose
Specific Options
/var/cache/fonts : Locally-generated fonts (optional)
/var/cache/man : Locally-formatted manual pages (optional)
/var/crash : System crash dumps (optional)
Purpose
/var/games : Variable game data (optional)
Purpose
/var/lib : Variable state information
Purpose
Requirements
Specific Options
/var/lib/<editor> : Editor backup files and state (optional)
/var/lib/hwclock : State directory for hwclock (optional)
/var/lib/misc : Miscellaneous variable data
/var/lock : Lock files
Purpose
/var/log : Log files and directories
Purpose
Specific Options
/var/mail : User mailbox files (optional)
Purpose
/var/opt : Variable data for /opt
Purpose
/var/run : Run-time variable data
Purpose
Requirements
/var/spool : Application spool data
Purpose
Specific Options
/var/spool/lpd : Line-printer daemon print queues (optional)
/var/spool/rwho : Rwhod files (optional)
/var/tmp : Temporary files preserved between system reboots
Purpose
/var/yp : Network Information Service (NIS) database files (optional)
Purpose
6. Operating System Specific Annex
Linux
/ : Root directory
/bin : Essential user command binaries (for use by all users)
/dev : Devices and special files
/etc : Host-specific system configuration
/lib64 and /lib32 : 64/32-bit libraries (architecture dependent)
/proc : Kernel and process information virtual filesystem
/sbin : Essential system binaries
/usr/include : Header files included by C programs
/usr/src : Source code
/var/spool/cron : cron and at jobs
7. Appendix
The FHS mailing list
Background of the FHS
General Guidelines
Scope
Acknowledgments
Contributors

Chapter 1. Introduction

Purpose

This standard enables:

 

  • Software to predict the location of installed files and directories, and

  • Users to predict the location of installed files and directories.

We do this by:

 

  • Specifying guiding principles for each area of the filesystem,

  • Specifying the minimum files and directories required,

  • Enumerating exceptions to the principles, and

  • Enumerating specific cases where there has been historical conflict.

The FHS document is used by:

 

  • Independent software suppliers to create applications which are FHS compliant, and work with distributions which are FHS complaint,

  • OS creators to provide systems which are FHS compliant, and

  • Users to understand and maintain the FHS compliance of a system.

The FHS document has a limited scope:

 

  • Local placement of local files is a local issue, so FHS does not attempt to usurp system administrators.

  • FHS addresses issues where file placements need to be coordinated between multiple parties such as local sites, distributions, applications, documentation, etc.


Conventions

We recommend that you read a typeset version of this document rather than the plain text version. In the typeset version, the names of files and directories are displayed in a constant-width font.

Components of filenames that vary are represented by a description of the contents enclosed in "<" and ">" characters, <thus>. Electronic mail addresses are also enclosed in "<" and ">" but are shown in the usual typeface.

Optional components of filenames are enclosed in "[" and "]" characters and may be combined with the "<" and ">" convention. For example, if a filename is allowed to occur either with or without an extension, it might be represented by <filename>[.<extension>].

Variable substrings of directory names and filenames are indicated by "*".

The sections of the text marked as Rationale are explanatory and are non-normative.


Chapter 2. The Filesystem

This standard assumes that the operating system underlying an FHS-compliant file system supports the same basic security features found in most UNIX filesystems.

It is possible to define two independent distinctions among files: shareable vs. unshareable and variable vs. static. In general, files that differ in either of these respects should be located in different directories. This makes it easy to store files with different usage characteristics on different filesystems.

"Shareable" files are those that can be stored on one host and used on others. "Unshareable" files are those that are not shareable. For example, the files in user home directories are shareable whereas device lock files are not.

"Static" files include binaries, libraries, documentation files and other files that do not change without system administrator intervention. "Variable" files are files that are not static.

 

Tip Rationale
 

Shareable files can be stored on one host and used on several others. Typically, however, not all files in the filesystem hierarchy are shareable and so each system has local storage containing at least its unshareable files. It is convenient if all the files a system requires that are stored on a foreign host can be made available by mounting one or a few directories from the foreign host.

Static and variable files should be segregated because static files, unlike variable files, can be stored on read-only media and do not need to be backed up on the same schedule as variable files.

Historical UNIX-like filesystem hierarchies contained both static and variable files under both /usr and /etc. In order to realize the advantages mentioned above, the /var hierarchy was created and all variable files were transferred from /usr to /var. Consequently /usr can now be mounted read-only (if it is a separate filesystem). Variable files have been transferred from /etc to /var over a longer period as technology has permitted.

Here is an example of a FHS-compliant system. (Other FHS-compliant layouts are possible.)

 

  shareable unshareable
static /usr /etc
  /opt /boot
variable /var/mail /var/run
  /var/spool/news /var/lock

 


Chapter 3. The Root Filesystem

Purpose

The contents of the root filesystem must be adequate to boot, restore, recover, and/or repair the system.

 

  • To boot a system, enough must be present on the root partition to mount other filesystems. This includes utilities, configuration, boot loader information, and other essential start-up data. /usr, /opt, and /var are designed such that they may be located on other partitions or filesystems.

  • To enable recovery and/or repair of a system, those utilities needed by an experienced maintainer to diagnose and reconstruct a damaged system must be present on the root filesystem.

  • To restore a system, those utilities needed to restore from system backups (on floppy, tape, etc.) must be present on the root filesystem.

 

Tip Rationale
 

The primary concern used to balance these considerations, which favor placing many things on the root filesystem, is the goal of keeping root as small as reasonably possible. For several reasons, it is desirable to keep the root filesystem small:

 

  • It is occasionally mounted from very small media.

  • The root filesystem contains many system-specific configuration files. Possible examples include a kernel that is specific to the system, a specific hostname, etc. This means that the root filesystem isn't always shareable between networked systems. Keeping it small on servers in networked systems minimizes the amount of lost space for areas of unshareable files. It also allows workstations with smaller local hard drives.

  • While you may have the root filesystem on a large partition, and may be able to fill it to your heart's content, there will be people with smaller partitions. If you have more files installed, you may find incompatibilities with other systems using root filesystems on smaller partitions. If you are a developer then you may be turning your assumption into a problem for a large number of users.

  • Disk errors that corrupt data on the root filesystem are a greater problem than errors on any other partition. A small root filesystem is less prone to corruption as the result of a system crash.

Applications must never create or require special files or subdirectories in the root directory. Other locations in the FHS hierarchy provide more than enough flexibility for any package.

 

Tip Rationale
 

There are several reasons why creating a new subdirectory of the root filesystem is prohibited:

 

  • It demands space on a root partition which the system administrator may want kept small and simple for either performance or security reasons.

  • It evades whatever discipline the system administrator may have set up for distributing standard file hierarchies across mountable volumes.

Distributions should not create new directories in the root hierarchy without extremely careful consideration of the consequences including for application portability.


Requirements

The following directories, or symbolic links to directories, are required in /.

 

Directory Description
bin Essential command binaries
boot Static files of the boot loader
dev Device files
etc Host-specific system configuration
lib Essential shared libraries and kernel modules
media Mount point for removeable media
mnt Mount point for mounting a filesystem temporarily
opt Add-on application software packages
sbin Essential system binaries
srv Data for services provided by this system
tmp Temporary files
usr Secondary hierarchy
var Variable data

 

Each directory listed above is specified in detail in separate subsections below. /usr and /var each have a complete section in this document due to the complexity of those directories.


Specific Options

The following directories, or symbolic links to directories, must be in /, if the corresponding subsystem is installed:

 

Directory Description
home User home directories (optional)
lib<qual> Alternate format essential shared libraries (optional)
root Home directory for the root user (optional)

 

Each directory listed above is specified in detail in separate subsections below.


/bin : Essential user command binaries (for use by all users)

Purpose

/bin contains commands that may be used by both the system administrator and by users, but which are required when no other filesystems are mounted (e.g. in single user mode). It may also contain commands which are used indirectly by scripts. [1]


Requirements

There must be no subdirectories in /bin.

The following commands, or symbolic links to commands, are required in /bin.

 

Command Description
cat Utility to concatenate files to standard output
chgrp Utility to change file group ownership
chmod Utility to change file access permissions
chown Utility to change file owner and group
cp Utility to copy files and directories
date Utility to print or set the system data and time
dd Utility to convert and copy a file
df Utility to report filesystem disk space usage
dmesg Utility to print or control the kernel message buffer
echo Utility to display a line of text
false Utility to do nothing, unsuccessfully
hostname Utility to show or set the system's host name
kill Utility to send signals to processes
ln Utility to make links between files
login Utility to begin a session on the system
ls Utility to list directory contents
mkdir Utility to make directories
mknod Utility to make block or character special files
more Utility to page through text
mount Utility to mount a filesystem
mv Utility to move/rename files
ps Utility to report process status
pwd Utility to print name of current working directory
rm Utility to remove files or directories
rmdir Utility to remove empty directories
sed The `sed' stream editor
sh The Bourne command shell
stty Utility to change and print terminal line settings
su Utility to change user ID
sync Utility to flush filesystem buffers
true Utility to do nothing, successfully
umount Utility to unmount file systems
uname Utility to print system information

 

If /bin/sh is not a true Bourne shell, it must be a hard or symbolic link to the real shell command.

The [ and test commands must be placed together in either /bin or /usr/bin.

 

Tip Rationale
 

For example bash behaves differently when called as sh or bash. The use of a symbolic link also allows users to easily see that /bin/sh is not a true Bourne shell.

The requirement for the [ and test commands to be included as binaries (even if implemented internally by the shell) is shared with the POSIX.2 standard.


Specific Options

The following programs, or symbolic links to programs, must be in /bin if the corresponding subsystem is installed:

 

Command Description
csh The C shell (optional)
ed The `ed' editor (optional)
tar The tar archiving utility (optional)
cpio The cpio archiving utility (optional)
gzip The GNU compression utility (optional)
gunzip The GNU uncompression utility (optional)
zcat The GNU uncompression utility (optional)
netstat The network statistics utility (optional)
ping The ICMP network test utility (optional)

 

If the gunzip and zcat programs exist, they must be symbolic or hard links to gzip. /bin/csh may be a symbolic link to /bin/tcsh or /usr/bin/tcsh.

 

Tip Rationale
 

The tar, gzip and cpio commands have been added to make restoration of a system possible (provided that / is intact).

Conversely, if no restoration from the root partition is ever expected, then these binaries might be omitted (e.g., a ROM chip root, mounting /usr through NFS). If restoration of a system is planned through the network, then ftp or tftp (along with everything necessary to get an ftp connection) must be available on the root partition.


/boot : Static files of the boot loader

Purpose

This directory contains everything required for the boot process except configuration files not needed at boot time and the map installer. Thus /boot stores data that is used before the kernel begins executing user-mode programs. This may include saved master boot sectors and sector map files. [2]


Specific Options

The operating system kernel must be located in either / or /boot. [3]


/dev : Device files

Purpose

The /dev directory is the location of special or device files.


Specific Options

If it is possible that devices in /dev will need to be manually created, /dev must contain a command named MAKEDEV, which can create devices as needed. It may also contain a MAKEDEV.local for any local devices.

If required, MAKEDEV must have provisions for creating any device that may be found on the system, not just those that a particular implementation installs.


/etc : Host-specific system configuration

Purpose

The /etc hierarchy contains configuration files. A "configuration file" is a local file used to control the operation of a program; it must be static and cannot be an executable binary. [4]


Requirements

No binaries may be located under /etc. [5]

The following directories, or symbolic links to directories are required in /etc:

 

Directory Description
opt Configuration for /opt
X11 Configuration for the X Window system (optional)
sgml Configuration for SGML (optional)
xml Configuration for XML (optional)

 


Specific Options

The following directories, or symbolic links to directories must be in /etc, if the corresponding subsystem is installed:

 

Directory Description
opt Configuration for /opt

 

The following files, or symbolic links to files, must be in /etc if the corresponding subsystem is installed: [6]

 

File Description
csh.login Systemwide initialization file for C shell logins (optional)
exports NFS filesystem access control list (optional)
fstab Static information about filesystems (optional)
ftpusers FTP daemon user access control list (optional)
gateways File which lists gateways for routed (optional)
gettydefs Speed and terminal settings used by getty (optional)
group User group file (optional)
host.conf Resolver configuration file (optional)
hosts Static information about host names (optional)
hosts.allow Host access file for TCP wrappers (optional)
hosts.deny Host access file for TCP wrappers (optional)
hosts.equiv List of trusted hosts for rlogin, rsh, rcp (optional)
hosts.lpd List of trusted hosts for lpd (optional)
inetd.conf Configuration file for inetd (optional)
inittab Configuration file for init (optional)
issue Pre-login message and identification file (optional)
ld.so.conf List of extra directories to search for shared libraries (optional)
motd Post-login message of the day file (optional)
mtab Dynamic information about filesystems (optional)
mtools.conf Configuration file for mtools (optional)
networks Static information about network names (optional)
passwd The password file (optional)
printcap The lpd printer capability database (optional)
profile Systemwide initialization file for sh shell logins (optional)
protocols IP protocol listing (optional)
resolv.conf Resolver configuration file (optional)
rpc RPC protocol listing (optional)
securetty TTY access control for root login (optional)
services Port names for network services (optional)
shells Pathnames of valid login shells (optional)
syslog.conf Configuration file for syslogd (optional)

 

mtab does not fit the static nature of /etc: it is excepted for historical reasons. [7]


/etc/opt : Configuration files for /opt

Purpose

Host-specific configuration files for add-on application software packages must be installed within the directory /etc/opt/<subdir>, where <subdir> is the name of the subtree in /opt where the static data from that package is stored.


Requirements

No structure is imposed on the internal arrangement of /etc/opt/<subdir>.

If a configuration file must reside in a different location in order for the package or system to function properly, it may be placed in a location other than /etc/opt/<subdir>.

 

Tip Rationale
 

Refer to the rationale for /opt.


/etc/X11 : Configuration for the X Window System (optional)

Purpose

/etc/X11 is the location for all X11 host-specific configuration. This directory is necessary to allow local control if /usr is mounted read only.


Specific Options

The following files, or symbolic links to files, must be in /etc/X11 if the corresponding subsystem is installed:

 

File Description
Xconfig

rpm vs. dpkg 常用參數對照

dpkg 是 Debian Linux 的套件管理程式, 對於習慣 rpm 的人來說, 一開始接觸 dpkg 時還真有點不太能適應, dpkg --help 查了又忘、忘了又查...

於是整理了一下兩種指令的對照表, 這樣就比較好上手了 :)

安裝

目的 rpm 用法 dpkg 用法
安裝指定套件 rpm -i pkgfile.rpm dpkg -i pkgfile.deb

查詢

目的 rpm 用法 dpkg 用法
顯示所有已安裝的套件名稱 rpm -qa dpkg -l (小寫 L)
顯示套件包含的所有檔案 rpm -ql pkgname (小寫 L) dpkg -L pkgname
顯示特定檔案所屬套件名稱 rpm -qf /path/to/file dpkg -S /path/to/file
查詢套件檔案資訊 rpm -qip pkgfile.rpm (顯示套件資訊)
rpm -qlp pkgfile.rpm (小寫 L, 顯示套件內所有檔案)
dpkg -I pkgfile.deb (大寫 I )
dpkg -c pkgfile.deb
顯示指定套件是否安裝 rpm -q pkgname (只顯示套件名稱)
rpm -qi pkgname (顯示套件資訊)
dpkg -l pkgname (小寫 L, 只列出簡潔資訊)
dpkg -s pkgname (顯示詳細資訊)
dpkg -p pkgname (顯示詳細資訊)

移除

目的 rpm 用法 dpkg 用法
移除指定套件 rpm -e pkgname dpkg -r pkgname (會留下套件設定檔)
dpkg -P pkgname (完全移除)

在 Debian 使用 alien 處理 RPM 套件

alien 可處理 .deb、.rpm、.slp、.tgz 等檔案格式, 進行轉檔或安裝.
於 Debian 安裝非 Debian 套件時, 可使用 alien 進行安裝.
安裝 alien 套件: apt-get install alien

  • 在 Debian 安裝 RPM 套件: alien -i quota-3.12-7.i386.rpm
  • 製作成 deb 的套件格式: alien -d quota-3.12-7.i386.rpm
  • 製作成 rpm 的套件格式: alien -r quota_3.12-6_i386.deb

Linux音频编程指南

肖文鹏 (xiaowp@263.net), 自由软件爱好者

2004 年 2 月 01 日

虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有着非常丰富的媒体功能,本文就是以多媒体应用中最基本的声音为对象,介绍如何在Linux平台下开发实际的音频应用程序,同时还给出了一些常用的音频编程框架。

一、数字音频

音频信号是一种连续变化的模拟信号,但计算机只能处理和记录二进制的数字信号,由自然音源得到的音频信号必须经过一定的变换,成为数字音频信号之后,才能送到计算机中作进一步的处理。

数 字音频系统通过将声波的波型转换成一系列二进制数据,来实现对原始声音的重现,实现这一步骤的设备常被称为模/数转换器(A/D)。A/D转换器以每秒钟 上万次的速率对声波进行采样,每个采样点都记录下了原始模拟声波在某一时刻的状态,通常称之为样本(sample),而每一秒钟所采样的数目则称为采样频 率,通过将一串连续的样本连接起来,就可以在计算机中描述一段声音了。对于采样过程中的每一个样本来说,数字音频系统会分配一定存储位来记录声波的振幅, 一般称之为采样分辩率或者采样精度,采样精度越高,声音还原时就会越细腻。

数字音频涉及到的概念非常多,对于在Linux下 进行音频编程的程序员来说,最重要的是理解声音数字化的两个关键步骤:采样和量化。采样就是每隔一定时间就读一次声音信号的幅度,而量化则是将采样得到的 声音信号幅度转换为数字值,从本质上讲,采样是时间上的数字化,而量化则是幅度上的数字化。下面介绍几个在进行音频编程时经常需要用到的技术指标:

  1. 采样频率
    采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。采样频率的选择应该遵循奈奎斯特(Harry Nyquist)采样理论:如果对某一模拟信号进行采样,则采样后可还原的最高信号频率只有采样频率的一半,或者说只要采样频率高于输入信号最高频率的两 倍,就能从采样信号系列重构原始信号。正常人听觉的频率范围大约在20Hz~20kHz之间,根据奈奎斯特采样理论,为了保证声音不失真,采样频率应该在 40kHz左右。常用的音频采样频率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等,如 果采用更高的采样频率,还可以达到DVD的音质。
  2. 量化位数
    量化位数是对模拟音频信号的幅度进行数字化,它决定了模拟信号数字化以后的动态范围,常用的有8位、12位和16位。量化位越高,信号的动态范围越大,数字化后的音频信号就越可能接近原始信号,但所需要的存贮空间也越大。
  3. 声道数
    声道数是反映音频数字化质量的另一个重要因素,它有单声道和双声道之分。双声道又称为立体声,在硬件中有两条线路,音质和音色都要优于单声道,但数字化后占据的存储空间的大小要比单声道多一倍。



回页首


二、声卡驱动

出于对安全性方面的考虑,Linux下的应用程序无法直接对声卡这类硬件设备进行操作,而是必须通过内核提供的驱动程序才能完成。在Linux上进行音频编程的本质就是要借助于驱动程序,来完成对声卡的各种操作。

对 硬件的控制涉及到寄存器中各个比特位的操作,通常这是与设备直接相关并且对时序的要求非常严格,如果这些工作都交由应用程序员来负责,那么对声卡的编程将 变得异常复杂而困难起来,驱动程序的作用正是要屏蔽硬件的这些底层细节,从而简化应用程序的编写。目前Linux下常用的声卡驱动程序主要有两种:OSS 和ALSA。

最早出现在Linux上的音频编程接口是OSS(Open Sound System),它由一套完整的内核驱动程序模块组成,可以为绝大多数声卡提供统一的编程接口。OSS出现的历史相对较长,这些内核模块中的一部分 (OSS/Free)是与Linux内核源码共同免费发布的,另外一些则以二进制的形式由4Front Technologies公司提供。由于得到了商业公司的鼎力支持,OSS已经成为在Linux下进行音频编程的事实标准,支持OSS的应用程序能够在绝 大多数声卡上工作良好。

虽然OSS已经非常成熟,但它毕竟是一个没有完全开放源代码的商业产品,ALSA(Advanced Linux Sound Architecture)恰好弥补了这一空白,它是在Linux下进行音频编程时另一个可供选择的声卡驱动程序。ALSA除了像OSS那样提供了一组内 核驱动程序模块之外,还专门为简化应用程序的编写提供了相应的函数库,与OSS提供的基于ioctl的原始编程接口相比,ALSA函数库使用起来要更加方 便一些。ALSA的主要特点有:

  • 支持多种声卡设备
  • 模块化的内核驱动程序
  • 支持SMP和多线程
  • 提供应用开发函数库
  • 兼容OSS应用程序

ALSA 和OSS最大的不同之处在于ALSA是由志愿者维护的自由项目,而OSS则是由公司提供的商业产品,因此在对硬件的适应程度上OSS要优于ALSA,它能 够支持的声卡种类更多。ALSA虽然不及OSS运用得广泛,但却具有更加友好的编程接口,并且完全兼容于OSS,对应用程序员来讲无疑是一个更佳的选择。




回页首


三、编程接口

如何对各种音频设备进行操作是在Linux上进行音频编程的关键,通过内核提供的一组系统调用,应用程序能够访问声卡驱动程序提供的各种音频设备接口,这是在Linux下进行音频编程最简单也是最直接的方法。

3.1 访问音频设备

无 论是OSS还是ALSA,都是以内核驱动程序的形式运行在Linux内核空间中的,应用程序要想访问声卡这一硬件设备,必须借助于Linux内核所提供的 系统调用(system call)。从程序员的角度来说,对声卡的操作在很大程度上等同于对磁盘文件的操作:首先使用open系统调用建立起与硬件间的联系,此时返回的文件描述 符将作为随后操作的标识;接着使用read系统调用从设备接收数据,或者使用write系统调用向设备写入数据,而其它所有不符合读/写这一基本模式的操 作都可以由ioctl系统调用来完成;最后,使用close系统调用告诉Linux内核不会再对该设备做进一步的处理。

  • open系统调用
    系统调用open可以获得对声卡的访问权,同时还能为随后的系统调用做好准备,其函数原型如下所示:
    int open(const char *pathname, int flags, int mode);
    

    参数pathname是将要被打开的设备文件的名称,对于声卡来讲一般是/dev/dsp。参数flags用来指明应该以什么方式打开设备文件,它可以是 O_RDONLY、O_WRONLY或者O_RDWR,分别表示以只读、只写或者读写的方式打开设备文件;参数mode通常是可选的,它只有在指定的设备 文件不存在时才会用到,指明新创建的文件应该具有怎样的权限。
    如果open系统调用能够成功完成,它将返回一个正整数作为文件标识符,在随后的系统调用中需要用到该标识符。如果open系统调用失败,它将返回-1,同时还会设置全局变量errno,指明是什么原因导致了错误的发生。
  • read系统调用
    系统调用read用来从声卡读取数据,其函数原型如下所示:
    int read(int fd, char *buf, size_t count);
    

    参数fd是设备文件的标识符,它是通过之前的open系统调用获得的;参数buf是指向缓冲区的字符指针,它用来保存从声卡获得的数据;参数count则 用来限定从声卡获得的最大字节数。如果read系统调用成功完成,它将返回从声卡实际读取的字节数,通常情况会比count的值要小一些;如果read系 统调用失败,它将返回-1,同时还会设置全局变量errno,来指明是什么原因导致了错误的发生。
  • write系统调用
    系统调用write用来向声卡写入数据,其函数原型如下所示:
    size_t write(int fd, const char *buf, size_t count);
    

    系统调用write和系统调用read在很大程度是类似的,差别只在于write是向声卡写入数据,而read则是从声卡读入数据。参数fd同样是设备文 件的标识符,它也是通过之前的open系统调用获得的;参数buf是指向缓冲区的字符指针,它保存着即将向声卡写入的数据;参数count则用来限定向声 卡写入的最大字节数。
    如果write系统调用成功完成,它将返回向声卡实际写入的字节数;如果read系统调用失败,它将返回-1,同时还会设置全局变量errno,来指明是 什么原因导致了错误的发生。无论是read还是write,一旦调用之后Linux内核就会阻塞当前应用程序,直到数据成功地从声卡读出或者写入为止。
  • ioctl系统调用
    系统调用ioctl可以对声卡进行控制,凡是对设备文件的操作不符合读/写基本模式的,都是通过ioctl来完成的,它可以影响设备的行为,或者返回设备的状态,其函数原型如下所示:
    int ioctl(int fd, int request, ...);
    

    参数fd是设备文件的标识符,它是在设备打开时获得的;如果设备比较复杂,那么对它的控制请求相应地也会有很多种,参数request的目的就是用来区分 不同的控制请求;通常说来,在对设备进行控制时还需要有其它参数,这要根据不同的控制请求才能确定,并且可能是与硬件设备直接相关的。
  • close系统调用
    当应用程序使用完声卡之后,需要用close系统调用将其关闭,以便及时释放占用的硬件资源,其函数原型如下所示:
    int close(int fd);
    

    参数fd是设备文件的标识符,它是在设备打开时获得的。一旦应用程序调用了close系统调用,Linux内核就会释放与之相关的各种资源,因此建议在不需要的时候尽量及时关闭已经打开的设备。

3.2 音频设备文件

对于Linux应用程序员来讲,音频编程接口实际上就是一组音频设备文件,通过它们可以从声卡读取数据,或者向声卡写入数据,并且能够对声卡进行控制,设置采样频率和声道数目等等。

  • /dev/sndstat
    设备文件/dev/sndstat是声卡驱动程序提供的最简单的接口,通常它是一个只读文件,作用也仅仅只限于汇报声卡的当前状态。一般说来,/dev/sndstat是提供给最终用户来检测声卡的,不宜用于程序当中,因为所有的信息都可以通过ioctl系统调用来获得。 Linux提供的cat命令可以很方便地从/dev/sndstat获得声卡的当前状态: [xiaowp@linuxgam sound]$ cat /dev/sndstat
  • /dev/dsp

    声 卡驱动程序提供的/dev/dsp是用于数字采样(sampling)和数字录音(recording)的设备文件,它对于Linux下的音频编程来讲非 常重要:向该设备写数据即意味着激活声卡上的D/A转换器进行放音,而向该设备读数据则意味着激活声卡上的A/D转换器进行录音。目前许多声卡都提供有多 个数字采样设备,它们在Linux下可以通过/dev/dsp1等设备文件进行访问。

    DSP是数字信号处理器 (Digital Signal Processor)的简称,它是用来进行数字信号处理的特殊芯片,声卡使用它来实现模拟信号和数字信号的转换。声卡中的DSP设备实际上包含两个组成部 分:在以只读方式打开时,能够使用A/D转换器进行声音的输入;而在以只写方式打开时,则能够使用D/A转换器进行声音的输出。严格说来,Linux下的 应用程序要么以只读方式打开/dev/dsp输入声音,要么以只写方式打开/dev/dsp输出声音,但事实上某些声卡驱动程序仍允许以读写的方式打开 /dev/dsp,以便同时进行声音的输入和输出,这对于某些应用场合(如IP电话)来讲是非常关键的。

    在从DSP 设备读取数据时,从声卡输入的模拟信号经过A/D转换器变成数字采样后的样本(sample),保存在声卡驱动程序的内核缓冲区中,当应用程序通过 read系统调用从声卡读取数据时,保存在内核缓冲区中的数字采样结果将被复制到应用程序所指定的用户缓冲区中。需要指出的是,声卡采样频率是由内核中的 驱动程序所决定的,而不取决于应用程序从声卡读取数据的速度。如果应用程序读取数据的速度过慢,以致低于声卡的采样频率,那么多余的数据将会被丢弃;如果 读取数据的速度过快,以致高于声卡的采样频率,那么声卡驱动程序将会阻塞那些请求数据的应用程序,直到新的数据到来为止。

    在 向DSP设备写入数据时,数字信号会经过D/A转换器变成模拟信号,然后产生出声音。应用程序写入数据的速度同样应该与声卡的采样频率相匹配,否则过慢的 话会产生声音暂停或者停顿的现象,过快的话又会被内核中的声卡驱动程序阻塞,直到硬件有能力处理新的数据为止。与其它设备有所不同,声卡通常不会支持非阻 塞(non-blocking)的I/O操作。

    无论是从声卡读取数据,或是向声卡写入数据,事实上都具有特定的格式 (format),默认为8位无符号数据、单声道、8KHz采样率,如果默认值无法达到要求,可以通过ioctl系统调用来改变它们。通常说来,在应用程 序中打开设备文件/dev/dsp之后,接下去就应该为其设置恰当的格式,然后才能从声卡读取或者写入数据。

  • /dev/audio
    /dev/audio类似于/dev/dsp,它兼容于Sun工作站上的音频设备,使用的是mu-law编码方式。如果声卡驱动程序提供了对/dev /audio的支持,那么在Linux上就可以通过cat命令,来播放在Sun工作站上用mu-law进行编码的音频文件:
    [xiaowp@linuxgam sound]$ cat audio.au > /dev/audio
    

    由于设备文件/dev/audio主要出于对兼容性的考虑,所以在新开发的应用程序中最好不要尝试用它,而应该以/dev/dsp进行替代。对于应用程序来说,同一时刻只能使用/dev/audio或者/dev/dsp其中之一,因为它们是相同硬件的不同软件接口。
  • /dev/mixer
    在声卡的硬件电路中,混音器(mixer)是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各 不相同。运行在Linux内核中的声卡驱动程序一般都会提供/dev/mixer这一设备文件,它是应用程序对混音器进行操作的软件接口。混音器电路通常 由两个部分组成:输入混音器(input mixer)和输出混音器(output mixer)。
    输入混音器负责从多个不同的信号源接收模拟信号,这些信号源有时也被称为混音通道或者混音设备。模拟信号通过增益控制器和由软件控制的音量调节器后,在不 同的混音通道中进行级别(level)调制,然后被送到输入混音器中进行声音的合成。混音器上的电子开关可以控制哪些通道中有信号与混音器相连,有些声卡 只允许连接一个混音通道作为录音的音源,而有些声卡则允许对混音通道做任意的连接。经过输入混音器处理后的信号仍然为模拟信号,它们将被送到A/D转换器 进行数字化处理。
    输出混音器的工作原理与输入混音器类似,同样也有多个信号源与混音器相连,并且事先都经过了增益调节。当输出混音器对所有的模拟信号进行了混合之后,通常 还会有一个总控增益调节器来控制输出声音的大小,此外还有一些音调控制器来调节输出声音的音调。经过输出混音器处理后的信号也是模拟信号,它们最终会被送 给喇叭或者其它的模拟输出设备。 对混音器的编程包括如何设置增益控制器的级别,以及怎样在不同的音源间进行切换,这些操作通常来讲是不连续的,而且不会像录音或者放音那样需要占用大量的 计算机资源。由于混音器的操作不符合典型的读/写操作模式,因此除了open和close两个系统调用之外,大部分的操作都是通过ioctl系统调用来完 成的。与/dev/dsp不同,/dev/mixer允许多个应用程序同时访问,并且混音器的设置值会一直保持到对应的设备文件被关闭为止。
    为了简化应用程序的设计,Linux上的声卡驱动程序大多都支持将混音器的ioctl操作直接应用到声音设备上,也就是说如果已经打开了/dev /dsp,那么就不用再打开/dev/mixer来对混音器进行操作,而是可以直接用打开/dev/dsp时得到的文件标识符来设置混音器。
  • /dev/sequencer
    目前大多数声卡驱动程序还会提供/dev/sequencer这一设备文件,用来对声卡内建的波表合成器进行操作,或者对MIDI总线上的乐器进行控制,一般只用于计算机音乐软件中。



回页首


四、应用框架

在Linux下进行音频编程时,重点在于如何正确地操作声卡驱动程序所提供的各种设备文件,由于涉及到的概念和因素比较多,所以遵循一个通用的框架无疑将有助于简化应用程序的设计。

4.1 DSP编程

对 声卡进行编程时首先要做的是打开与之对应的硬件设备,这是借助于open系统调用来完成的,并且一般情况下使用的是/dev/dsp文件。采用何种模式对 声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开, 并且还要依赖于驱动程序的具体实现。Linux允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换, 建议在进行音频编程时只要有可能就尽量使用只读或者只写的方式打开设备文件,因为这样不仅能够充分利用声卡的硬件资源,而且还有利于驱动程序的优化。下面 的代码示范了如何以只写方式打开声卡进行放音(playback)操作:

int handle = open("/dev/dsp", O_WRONLY);
if (handle == -1) {
	perror("open /dev/dsp");
	return -1;
}

运行在Linux内核中的声卡驱动程序专门维护了一个 缓冲区,其大小会影响到放音和录音时的效果,使用ioctl系统调用可以对它的尺寸进行恰当的设置。调节驱动程序中缓冲区大小的操作不是必须的,如果没有 特殊的要求,一般采用默认的缓冲区大小也就可以了。但需要注意的是,缓冲区大小的设置通常应紧跟在设备文件打开之后,这是因为对声卡的其它操作有可能会导 致驱动程序无法再修改其缓冲区的大小。下面的代码示范了怎样设置声卡驱动程序中的内核缓冲区的大小:

int setting = 0xnnnnssss;
int result = ioctl(handle, SNDCTL_DSP_SETFRAGMENT, &setting);
if (result == -1) {
	perror("ioctl buffer size");
	return -1;
}
// 检查设置值的正确性

在设置缓冲区大小时,参数setting实际上由两部 分组成,其低16位标明缓冲区的尺寸,相应的计算公式为buffer_size = 2^ssss,即若参数setting低16位的值为16,那么相应的缓冲区的大小会被设置为65536字节。参数setting的高16位则用来标明分 片(fragment)的最大序号,它的取值范围从2一直到0x7FFF,其中0x7FFF表示没有任何限制。

接下来要做的是设置声卡工作时的声道(channel)数目,根据硬件设备和驱动程序的具体情况,可以将其设置为0(单声道,mono)或者1(立体声,stereo)。下面的代码示范了应该怎样设置声道数目:

int channels = 0; // 0=mono 1=stereo
int result = ioctl(handle, SNDCTL_DSP_STEREO, &channels);
if ( result == -1 ) {
	perror("ioctl channel number");
	return -1;
}
if (channels != 0) {
	// 只支持立体声
}

采样格式和采样频率是在进行音频编程时需要考虑的另一个问题,声卡支持的所有采样格式可以在头文件soundcard.h中找到,而通过ioctl系统调用则可以很方便地更改当前所使用的采样格式。下面的代码示范了如何设置声卡的采样格式:

int format = AFMT_U8;
int result = ioctl(handle, SNDCTL_DSP_SETFMT, &format);
if ( result == -1 ) {
	perror("ioctl sample format");
	return -1;
}
// 检查设置值的正确性

声卡采样频率的设置也非常容易,只需在调用ioctl 时将第二个参数的值设置为SNDCTL_DSP_SPEED,同时在第三个参数中指定采样频率的数值就行了。对于大多数声卡来说,其支持的采样频率范围一 般为5kHz到44.1kHz或者48kHz,但并不意味着该范围内的所有频率都会被硬件支持,在Linux下进行音频编程时最常用到的几种采样频率是 11025Hz、16000Hz、22050Hz、32000Hz和44100Hz。下面的代码示范了如何设置声卡的采样频率:

int rate = 22050;
int result = ioctl(handle, SNDCTL_DSP_SPEED, &rate);
if ( result == -1 ) {
	perror("ioctl sample format");
	return -1;
}
// 检查设置值的正确性

4.2 Mixer编程

声卡上的混音器由多个混音通道组成,它们可以通过驱动程序提供的设备文件/dev/mixer进行编程。对混音器的操作是通过ioctl系统调用来完成的,并且所有控制命令都由SOUND_MIXER或者MIXER开头,表1列出了常用的几个混音器控制命令:

名 称 作 用
SOUND_MIXER_VOLUME 主音量调节
SOUND_MIXER_BASS 低音控制
SOUND_MIXER_TREBLE 高音控制
SOUND_MIXER_SYNTH FM合成器
SOUND_MIXER_PCM 主D/A转换器
SOUND_MIXER_SPEAKER PC喇叭
SOUND_MIXER_LINE 音频线输入
SOUND_MIXER_MIC 麦克风输入
SOUND_MIXER_CD CD输入
SOUND_MIXER_IMIX 回放音量
SOUND_MIXER_ALTPCM 从D/A 转换器
SOUND_MIXER_RECLEV 录音音量
SOUND_MIXER_IGAIN 输入增益
SOUND_MIXER_OGAIN 输出增益
SOUND_MIXER_LINE1 声卡的第1输入
SOUND_MIXER_LINE2 声卡的第2输入
SOUND_MIXER_LINE3 声卡的第3输入

表1 混音器命令

对 声卡的输入增益和输出增益进行调节是混音器的一个主要作用,目前大部分声卡采用的是8位或者16位的增益控制器,但作为程序员来讲并不需要关心这些,因为 声卡驱动程序会负责将它们变换成百分比的形式,也就是说无论是输入增益还是输出增益,其取值范围都是从0到100。在进行混音器编程时,可以使用 SOUND_MIXER_READ宏来读取混音通道的增益大小,例如在获取麦克风的输入增益时,可以使用如下的代码:

int vol;
ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);
printf("Mic gain is at %d %%\n", vol);

对于只有一个混音通道的单声道设备来说,返回的增益大 小保存在低位字节中。而对于支持多个混音通道的双声道设备来说,返回的增益大小实际上包括两个部分,分别代表左、右两个声道的值,其中低位字节保存左声道 的音量,而高位字节则保存右声道的音量。下面的代码可以从返回值中依次提取左右声道的增益大小:

int left, right;
left = vol & 0xff;
right = (vol & 0xff00) >> 8;
printf("Left gain is %d %%, Right gain is %d %%\n", left, right);

类似地,如果想设置混音通道的增益大小,则可以通过SOUND_MIXER_WRITE宏来实现,此时遵循的原则与获取增益值时的原则基本相同,例如下面的语句可以用来设置麦克风的输入增益:

vol = (right << 8) + left;
ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);

在编写实用的音频程序时,混音器是在涉及到兼容性时需 要重点考虑的一个对象,这是因为不同的声卡所提供的混音器资源是有所区别的。声卡驱动程序提供了多个ioctl系统调用来获得混音器的信息,它们通常返回 一个整型的位掩码(bitmask),其中每一位分别代表一个特定的混音通道,如果相应的位为1,则说明与之对应的混音通道是可用的。例如通过 SOUND_MIXER_READ_DEVMASK返回的位掩码,可以查询出能够被声卡支持的每一个混音通道,而通过 SOUND_MIXER_READ_RECMAS返回的位掩码,则可以查询出能够被当作录音源的每一个通道。下面的代码可以用来检查CD输入是否是一个有 效的混音通道:

  ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
if (devmask & SOUND_MIXER_CD)
  printf("The CD input is supported");
  

如果进一步还想知道其是否是一个有效的录音源,则可以使用如下语句:

ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);
if (recmask & SOUND_MIXER_CD)
  printf("The CD input can be a recording source");

目前大多数声卡提供多个录音源,通过 SOUND_MIXER_READ_RECSRC可以查询出当前正在使用的录音源,同一时刻能够使用几个录音源是由声卡硬件决定的。类似地,使用 SOUND_MIXER_WRITE_RECSRC可以设置声卡当前使用的录音源,例如下面的代码可以将CD输入作为声卡的录音源使用:

devmask = SOUND_MIXER_CD;
ioctl(fd, SOUND_MIXER_WRITE_DEVMASK, &devmask);

此外,所有的混音通道都有单声道和双声道的区别,如果需要知道哪些混音通道提供了对立体声的支持,可以通过SOUND_MIXER_READ_STEREODEVS来获得。

4.3 音频录放框架

下面给出一个利用声卡上的DSP设备进行声音录制和回放的基本框架,它的功能是先录制几秒种音频数据,将其存放在内存缓冲区中,然后再进行回放,其所有的功能都是通过读写/dev/dsp设备文件来完成的:

/*
 * sound.c
 */
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/soundcard.h>
#define LENGTH 3    /* 存储秒数 */
#define RATE 8000   /* 采样频率 */
#define SIZE 8      /* 量化位数 */
#define CHANNELS 1  /* 声道数目 */
/* 用于保存数字音频数据的内存缓冲区 */
unsigned char buf[LENGTH*RATE*SIZE*CHANNELS/8];
int main()
{
  int fd;	/* 声音设备的文件描述符 */
  int arg;	/* 用于ioctl调用的参数 */
  int status;   /* 系统调用的返回值 */
  /* 打开声音设备 */
  fd = open("/dev/dsp", O_RDWR);
  if (fd < 0) {
    perror("open of /dev/dsp failed");
    exit(1);
  }
  /* 设置采样时的量化位数 */
  arg = SIZE;
  status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);
  if (status == -1)
    perror("SOUND_PCM_WRITE_BITS ioctl failed");
  if (arg != SIZE)
    perror("unable to set sample size");
  /* 设置采样时的声道数目 */
  arg = CHANNELS; 
  status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);
  if (status == -1)
    perror("SOUND_PCM_WRITE_CHANNELS ioctl failed");
  if (arg != CHANNELS)
    perror("unable to set number of channels");
  /* 设置采样时的采样频率 */
  arg = RATE;
  status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);
  if (status == -1)
    perror("SOUND_PCM_WRITE_WRITE ioctl failed");
  /* 循环,直到按下Control-C */
  while (1) {
    printf("Say something:\n");
    status = read(fd, buf, sizeof(buf)); /* 录音 */
    if (status != sizeof(buf))
      perror("read wrong number of bytes");
    printf("You said:\n");
    status = write(fd, buf, sizeof(buf)); /* 回放 */
    if (status != sizeof(buf))
      perror("wrote wrong number of bytes");
    /* 在继续录音前等待回放结束 */
    status = ioctl(fd, SOUND_PCM_SYNC, 0); 
    if (status == -1)
      perror("SOUND_PCM_SYNC ioctl failed");
  }
}

4.4 混音器框架

下面再给出一个对混音器进行编程的基本框架,利用它可以对各种混音通道的增益进行调节,其所有的功能都是通过读写/dev/mixer设备文件来完成的:

/*
 * mixer.c
 */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/soundcard.h>
/* 用来存储所有可用混音设备的名称 */
const char *sound_device_names[] = SOUND_DEVICE_NAMES;
int fd;                  /* 混音设备所对应的文件描述符 */
int devmask, stereodevs; /* 混音器信息对应的位图掩码 */
char *name;
/* 显示命令的使用方法及所有可用的混音设备 */
void usage()
{
  int i;
  fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>\n"
	  "       %s <device> <gain%%>\n\n"
	  "Where <device> is one of:\n", name, name);
  for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++)
    if ((1 << i) & devmask) /* 只显示有效的混音设备 */
      fprintf(stderr, "%s ", sound_device_names[i]);
  fprintf(stderr, "\n");
  exit(1);
}
int main(int argc, char *argv[])
{
  int left, right, level;  /* 增益设置 */
  int status;              /* 系统调用的返回值 */
  int device;              /* 选用的混音设备 */
  char *dev;               /* 混音设备的名称 */
  int i;
  name = argv[0];
  /* 以只读方式打开混音设备 */
  fd = open("/dev/mixer", O_RDONLY);
  if (fd == -1) {
    perror("unable to open /dev/mixer");
    exit(1);
  }
  
  /* 获得所需要的信息 */
  status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
  if (status == -1)
    perror("SOUND_MIXER_READ_DEVMASK ioctl failed");
  status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);
  if (status == -1)
    perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");
  /* 检查用户输入 */
  if (argc != 3 && argc != 4)
    usage();
  /* 保存用户输入的混音器名称 */
  dev = argv[1];
  /* 确定即将用到的混音设备 */
  for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++)
    if (((1 << i) & devmask) && !strcmp(dev, sound_device_names[i]))
      break;
  if (i == SOUND_MIXER_NRDEVICES) { /* 没有找到匹配项 */
    fprintf(stderr, "%s is not a valid mixer device\n", dev);
    usage();
  }
  /* 查找到有效的混音设备 */
  device = i;
  /* 获取增益值 */
  if (argc == 4) {
    /* 左、右声道均给定 */
    left  = atoi(argv[2]);
    right = atoi(argv[3]);
  } else {
    /* 左、右声道设为相等 */
    left  = atoi(argv[2]);
    right = atoi(argv[2]);
  }
  
  /* 对非立体声设备给出警告信息 */
  if ((left != right) && !((1 << i) & stereodevs)) {
    fprintf(stderr, "warning: %s is not a stereo device\n", dev);
  }
  
  /* 将两个声道的值合到同一变量中 */
  level = (right << 8) + left;
  
  /* 设置增益 */
  status = ioctl(fd, MIXER_WRITE(device), &level);
  if (status == -1) {
    perror("MIXER_WRITE ioctl failed");
    exit(1);
  }
  /* 获得从驱动返回的左右声道的增益 */
  left  = level & 0xff;
  right = (level & 0xff00) >> 8;
  /* 显示实际设置的增益 */
  fprintf(stderr, "%s gain set to %d%% / %d%%\n", dev, left, right);
  /* 关闭混音设备 */
  close(fd);
  return 0;
}

编译好上面的程序之后,先不带任何参数执行一遍,此时会列出声卡上所有可用的混音通道:

[xiaowp@linuxgam sound]$ ./mixer
usage: ./mixer <device> <left-gain%> <right-gain%>
       ./mixer <device> <gain%>
 
Where <device> is one of:
vol pcm speaker line mic cd igain line1 phin video

之后就可以很方便地设置各个混音通道的增益大小了,例如下面的命令就能够将CD输入的左、右声道的增益分别设置为80%和90%:

[xiaowp@linuxgam sound]$ ./mixer cd 80 90
cd gain set to 80% / 90%




回页首


五、小结

随 着Linux平台下多媒体应用的逐渐深入,需要用到数字音频的场合必将越来越广泛。虽然数字音频牵涉到的概念非常多,但在Linux下进行最基本的音频编 程却并不十分复杂,关键是掌握如何与OSS或者ALSA这类声卡驱动程序进行交互,以及如何充分利用它们提供的各种功能,熟悉一些最基本的音频编程框架和 模式对初学者来讲大有裨益。



参考资料

  • 1. OSS是Linux上最早出现的声卡驱动程序,http://www.opensound.com是它的核心网站,从中可以了解到许多与OSS相关的信息。
     
  • 2. ALSA是目前广泛使用的Linux声卡驱动程序,并且提供了一些库函数来简化音频程序的编写,在其官方网站http://www.alsa-project.org/上可以了解到ALSA的许多信息,并能够下载到最新的驱动程序和工具软件。
     
  • 3. Ken C. Pohlmann著,苏菲译,数字音频原理与应用(第四合版),北京:电子工业出版社,2002
     
  • 4. 钟玉琢等编著,多媒体技术及其应用,北京:机械工业出版社,2003


关于作者

 

本文作者肖文鹏是一名自由软件爱好者,主要从事操作系统和分布式计算环境的研究,喜爱Linux和Python。你可以通过 xiaowp@263.net与他取得联系。

linux下在终端中命令方式录音

用 cat 就可以了
# cat /dev/audio > test0.au

按 ctrl + c 停止录音

用 dd 也可以
# dd if=/dev/audio of=test01.au bs=1m count=1

 

/* 此文件中定义了下面所有形如SND_的变量*/
#include
#include
#include
#include
#include
#include
int main()
{
        /* id:读取音频文件描述符;fd:写入的文件描述符。i,j为临时变量*/
        int id,fd,i,j;
        /* 存储音频数据的缓冲区,可以调整*/
        char testbuf[4096];
        /* 打开声卡设备,失败则退出*/
        if ( ( id = open ( "/dev/audio", O_RDWR ) ) 0){
                        write(fd,testbuf,i);
                        j++;
                }
        }
        /* 关闭输入、输出文件*/
        close(fd);
        close(id);
        exit(0);
}

 

/* 此文件中定义了下面所有形如SND_的变量*/
#include <sys/soundcard.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdlib.h>

int main()
{
 /* id:读取音频文件描述符;fd:写入的文件描述符。i,j为临时变量*/
 int id,fd,i,j;
 /* 存储音频数据的缓冲区,可以调整*/
 char testbuf[4096];
 /* 打开声卡设备,失败则退出*/
 if ( ( id = open ( "/dev/audio", O_RDWR ) ) < 0 ) {
  fprintf (stderr, " Can't open sound device!\n");
  exit ( -1 ) ;
 }
 /* 打开输出文件,失败则退出*/
 if ( ( fd = open ("test.wav",O_RDWR))<0){
  fprintf ( stderr, " Can't open output file!\n");
  exit (-1 );
 }
 /* 设置适当的参数,使得声音设备工作正常*/
 /* 详细情况请参考Linux关于声卡编程的文档*/
 i=0;
 ioctl (id,SNDCTL_DSP_RESET,(char *)&i) ;
 ioctl (id,SNDCTL_DSP_SYNC,(char *)&i);
 i=1;
 ioctl (id,SNDCTL_DSP_NONBLOCK,(char *)&i);
 i=8000;
 ioctl (id,SNDCTL_DSP_SPEED,(char *)&i);
 i=1;
 ioctl (id,SNDCTL_DSP_CHANNELS,(char *)&i);
 i=8;
 ioctl (id,SNDCTL_DSP_SETFMT,(char *)&i);
 i=3;
 ioctl (id,SNDCTL_DSP_SETTRIGGER,(char *)&i);
 i=3;
 ioctl (id,SNDCTL_DSP_SETFRAGMENT,(char *)&i);
 i=1;
 ioctl (id,SNDCTL_DSP_PROFILE,(char *)&i);
 /* 读取一定数量的音频数据,并将之写到输出文件中去*/
 for ( j=0; j<100;){
  i=read(id,testbuf,4096);
  if(i>0){
   write(fd,testbuf,i);
   j++;
  }
 }
 /* 关闭输入、输出文件*/
 close(fd);
 close(id);
 exit(0);
}

检测网卡物理连接的方法

/* Check network adapter is up or down */
// Note: one or two adapter drivers may be not support this method
// exemple: based virtual machine

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/sockios.h>
#include <linux/types.h>

const char* VERSION = "0.6.0.48 --maintain by isnowran";

struct mii_data
{
        __u16 phy_id;
        __u16 reg_num;
        __u16 val_in;
        __u16 val_out;
};

int main( int argc, char* argv[] )
{
        if( argc != 2 )
        {
                printf( "Version: %s\n", VERSION );
                printf( "Useage: argv[0] ethNO.\n" );
                return 0;
        }

        int skfd = 0;
        if( ( skfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 )
        {
                perror( "socket" );
                return -1;
        }

        struct ifreq ifr;
        bzero( &ifr, sizeof( ifr ) );
        strncpy( ifr.ifr_name, argv[1], IFNAMSIZ - 1 );
        ifr.ifr_name[IFNAMSIZ - 1] = 0;
        if( ioctl( skfd, SIOCGMIIPHY, &ifr ) < 0 )
        {
                perror( "ioctl" );
                return -1;
        }

        struct mii_data* mii = NULL;
        mii = (struct mii_data*)&ifr.ifr_data;
        mii->reg_num = 0x01;
        if( ioctl( skfd, SIOCGMIIREG, &ifr ) < 0 )
        {
                perror( "ioctl2" );
                return -1;
        }

        if( mii->val_out & 0x0004 )
                printf( "Linkup\n" );
        else
                printf( "Linkdown\n" );

        close( skfd );
        return 0;
}

dbus基础

这是以前学习dbus时候写的使用dbus API来接收消息的一个比较全的例子,目的是熟悉dbus的c api的功能。今天又温习了一下,贴出来永久保留:), 有时间把原理写下,kde4都用它了,应该很重要。
/**
* the demo is to demonstrate the whole process of setting up
* a dbus message loop manually. for simplicity, we connect to
* session bus instead of coding a server from the grand up.
* the best of dbus is that we can register our own callbacks in
* all most every step of message process. by using notify functions
* the main loop of application awares of the changes, including coming
* of raw message data from the transport, new outgoing message, incoming
* message queue is empty and so on.
*/
#include dbus/dbus.h>
#include stdbool.h>
#include unistd.h>
#include stdio.h>
#include stdlib.h>
#include stdarg.h>
#include string.h>
#include sys/select.h>
/**
* for every connection, there is a transport mechanism ( here
* maybe is unix domain socket ). data come and go from the connection,
* a watch is used to monitor incoming or outgoing data flow or exception.
* a watch bind with a fd (i know here fd is owned by the socket),
* when fd is ready to read data or write from transport, a notify
* event occurs.
*/
struct watchlist_t {
    DBusWatch *watch;
    struct watchlist_t *next;
};
static struct watchlist_t *watchlist = NULL;
/**
* when you send a message typed method_call with a reply, you
* may need to set up a timeout handler. when a reply available,
* a timeout event ocurrs.
*/
static struct timeoutlist_t {
    DBusTimeout *timeout;
    struct timeoutlist_t *next;
} *timeoutlist = NULL;
/**
* you can register any path you like, just follow the dbus specification,
* such as "/org", "/org/redflag", "/com" etc. i think dbus internally
* splits all path into components ("org", "redflag", "com") and arranges
* them into a tree, every node bind a handler. if one node has no
* handler registered, it processed by dbus( use
* DBUS_HANDLER_RESULT_NOT_YET_HANDLED to notify dbus).
*/
char *objectPaths[] = {
    "/org/freedesktop",
    "/com",
    "/com/redflag",
    "/org/freedesktop/DBus",
    "/org/freedesktop/csy",
    "/com/redflag/csy"
};
/** object handlers and object path handlers */
DBusHandlerResult object_dbus_handler(DBusConnection*, DBusMessage*, void*);
DBusHandlerResult object_csy_handler(DBusConnection*, DBusMessage*, void*);
DBusHandlerResult subsection_com_redflag_handler(DBusConnection*,
        DBusMessage*, void*);
DBusHandlerResult subsection_com_handler(DBusConnection*,
        DBusMessage*, void*);
DBusHandlerResult subsection_org_freedesktop_handler(DBusConnection*,
        DBusMessage*, void*);
void object_unregister_handler(DBusConnection*, void*);
DBusObjectPathVTable objectPathVTable[] = {
    {
        .unregister_function = NULL,
        .message_function = subsection_org_freedesktop_handler
    },
    {   
        .unregister_function = NULL,
        .message_function = subsection_com_handler
    },
    {
        .unregister_function = NULL,
        .message_function = subsection_com_redflag_handler
    },
    {
        .unregister_function = NULL,
        .message_function = object_dbus_handler
    },
    {
        .unregister_function = object_unregister_handler,
        .message_function = object_csy_handler
    },
    {
        .unregister_function = object_unregister_handler,
        .message_function = object_csy_handler
    }
};
static struct seriallist_t {
    dbus_uint32_t serial;
    struct seriallist_t *next;
} *seriallist = NULL;
/**------------- debug facilities --------------------------------*/
void err_quit(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    fprintf(stderr, fmt, args);
    va_end(args);
    exit(1);   
}
/**
* for debug purpose
*/
char* _verbose_watch(DBusWatch *watch) {
    char *s_flags[] = {
        "readable",
        "writable"
    };
    char p[1024] = "", ret[1024] = "";
    if (dbus_watch_get_flags(watch) & DBUS_WATCH_READABLE )
        strncpy(p, s_flags[0], strlen(s_flags[0]));
    else if (dbus_watch_get_flags(watch) & DBUS_WATCH_WRITABLE ) {
        if ( p[0] )
            strncat(p, "&", strlen("&"));
        strncat(p, s_flags[1], strlen(s_flags[1]));
    }
    sprintf(ret, ":%s", dbus_watch_get_fd(watch), p);
    return ret;
}
char* _verbose_message(DBusMessage *msg) {
    char s_msg[1024] = "", ret[1024] = "";
    int bc = sprintf(s_msg, "\ttype: %s\n\tpath: %s\n"
            "\tmember: %s\n\t",
            dbus_message_type_to_string(dbus_message_get_type(msg)),
            dbus_message_get_path(msg),
            dbus_message_get_member(msg));
    strncpy(ret, s_msg, bc+1);
   
    if (dbus_message_get_serial(msg)) {
        bc = sprintf(s_msg, "serial: %ud\n\t",
                dbus_message_get_serial(msg));
        strncat(ret, s_msg, bc+1);
    }
    if (dbus_message_get_reply_serial(msg)) {
        bc = sprintf(s_msg, "reply_serial: %ud\n\t",
                dbus_message_get_reply_serial(msg));
        strncat(ret, s_msg, bc+1);
    }
    DBusMessageIter args, subargs;
    char *s;
    int i;
    dbus_message_iter_init(msg, &args);
    bc = sprintf(s_msg, "args: ");
    strncat(ret, s_msg, bc+1);
    /** here is not the best way to parse the params, i just want to test some of
     * nested situations and different types of params. */
    while (DBUS_TYPE_INVALID != dbus_message_iter_get_arg_type(&args)) {
        // demo here: only care about int and string, igore other types
        switch (dbus_message_iter_get_arg_type(&args)) {
        case DBUS_TYPE_STRING:
            dbus_message_iter_get_basic(&args, &s);
            bc = sprintf(s_msg, " %s", s);
            strncat(ret, s_msg, bc+1);
            break;
        case DBUS_TYPE_INT32:
            dbus_message_iter_get_basic(&args, &i);
            bc = sprintf(s_msg, " %d", i);
            strncat(ret, s_msg, bc+1);
            break;
        case DBUS_TYPE_ARRAY:
            dbus_message_iter_recurse(&args, &subargs);
            strcat(ret, " [ ");
            while (dbus_message_iter_get_arg_type(&subargs)
                    != DBUS_TYPE_INVALID) {
                switch (dbus_message_iter_get_arg_type(&subargs)) {
                case DBUS_TYPE_STRING:
                    dbus_message_iter_get_basic(&subargs, &s);
                    bc = sprintf(s_msg, " %s", s);
                    strncat(ret, s_msg, bc+1);
                    break;
                case DBUS_TYPE_INT32:
                    dbus_message_iter_get_basic(&subargs, &i);
                    bc = sprintf(s_msg, " %d", i);
                    strncat(ret, s_msg, bc+1);
                    break;
                }
                dbus_message_iter_next(&subargs);
            }
            strcat(ret, " ] ");
        }
        dbus_message_iter_next(&args);
    }
    return ret;
}
char* _verbose_timeout(DBusTimeout *timeout) {
    char ret[1024] = "";
    sprintf(ret, "timeout: \n",
            (unsigned int)timeout, dbus_timeout_get_interval(timeout));
    return ret;        
}
/** -------------------------------------------------------*/
DBusHandlerResult handle_method_return(DBusConnection *conn,
        DBusMessage *reply) {
    struct seriallist_t *l = seriallist;
    while (l != NULL) {
        if (l->serial == dbus_message_get_reply_serial(reply)) {        
            printf("reply_msg:\t%s\n", _verbose_message(reply));
            return DBUS_HANDLER_RESULT_HANDLED;
        }
        l = l->next;
    }
    return DBUS_HANDLER_RESULT_HANDLED;
}
int reply_to_method_call(DBusMessage *msg, DBusConnection *conn) {
    DBusMessage *reply;
    DBusMessageIter args;
    DBusError err;
    // suppose we expect two argument, one is id, and second is
    // a string.
    if (!dbus_message_iter_init(msg, &args))
        err_quit("arg init error.\n");
    dbus_int32_t id;
    char *content;
    dbus_error_init( &err );
    dbus_message_get_args( msg, &err,
            DBUS_TYPE_INT32, &id,
            DBUS_TYPE_STRING, &content,
            DBUS_TYPE_INVALID );
    if (dbus_error_is_set(&err))
        err_quit("get arg error.\n");
    reply = dbus_message_new_method_return(msg);
    if (NULL == reply)
        err_quit("Memory is not enough.\n");
    printf("received call args: %d: %s\n", id, content);
    const char *comment = "reply to method call from com.redflag.csy.";
    dbus_message_iter_init_append( reply, &args );
    dbus_message_append_args ( reply,
            DBUS_TYPE_STRING, &comment,
            DBUS_TYPE_INVALID );
    dbus_uint32_t serial;
    dbus_connection_send( conn, reply, &serial );
    dbus_connection_flush( conn );
   
    printf( "build reply msg and send: \n\t%s\n", _verbose_message(reply) );
    dbus_message_unref( reply );
}
dbus_bool_t watchAddNotify(DBusWatch *watch, void *data) {
    struct watchlist_t *l;
    for (l = watchlist; l != NULL; l = l->next) {
        if (l->watch == watch)
            return TRUE;
    }
    printf("watchAdd: %s\n", _verbose_watch(watch));   
    l = dbus_new(struct watchlist_t, 1);
    if ( NULL == l )
        return FALSE;
    l->watch = watch;
    l->next = watchlist;
    watchlist = l;
    return TRUE;
}
void watchRemoveNotify(DBusWatch *watch, void *data) {
    struct watchlist_t *l, *pre;
    for ( pre = l = watchlist; l != NULL; pre = l, l = l->next ) {
        if (l->watch == watch) {
            printf("watchRemove: \n", dbus_watch_get_fd(watch));
            if ( l == watchlist ) {
                watchlist = l->next;
                dbus_free(l);
            } else {
                pre->next = l->next;
                dbus_free(l);
            }
            break;
        }
    }        
}
void watchToggleNotify(DBusWatch *watch, void *data) {
    if (watch == NULL)
        err_quit("line %d: watch should not be null.", __LINE__);
    printf( "toggleNotify: watch %d toogled %s",
            dbus_watch_get_fd(watch),
            dbus_watch_get_enabled(watch)?"enable":"disabled" );
}
/**
* process any watches added to connection
*/
dbus_bool_t watchHandler() {
    struct watchlist_t *l;
    fd_set rfds, wfds, efds;
    int maxid = -1, fd;
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&efds);
    // prepare all readble and writable fds
    for (l = watchlist; l != NULL; l = l->next) {
        if (!dbus_watch_get_enabled(l->watch))
            continue;
        fd = dbus_watch_get_fd(l->watch);
        if ( fd & DBUS_WATCH_READABLE ) {
            FD_SET(fd, &rfds);
            maxid = (maxid  fd? fd: maxid);
        }
        if ( fd & DBUS_WATCH_WRITABLE ) {
            FD_SET(fd, &wfds);
            maxid = (maxid  fd? fd: maxid);
        }
        if ( fd & DBUS_WATCH_ERROR ) {
            FD_SET(fd, &efds);
            maxid = (maxid  fd? fd: maxid);
        }
    }
    int ret = select(maxid+1, &rfds, &wfds, &efds, NULL);
    if (ret = 0)
        return;
   
    // call dbus_watch_handle is a must, it uses internal predefined
    // watch handler to do some thing, but i am not  quite sure what
    // it is right now.
    for (l = watchlist; l != NULL; l = l->next) {
        fd = dbus_watch_get_fd(l->watch);
   
        if (FD_ISSET(fd, &rfds))
            dbus_watch_handle(l->watch, DBUS_WATCH_READABLE);
         
        if (FD_ISSET(fd, &wfds))
            dbus_watch_handle(l->watch, DBUS_WATCH_WRITABLE);
        
        if (FD_ISSET(fd, &efds))
            dbus_watch_handle(l->watch, DBUS_WATCH_ERROR);   
    }
}
/**---- timeout process functions ------------------------------*/
dbus_bool_t timeoutAddNotify(DBusTimeout *timeout, void *data) {
    struct timeoutlist_t *l;
    for (l = timeoutlist; l != NULL; l = l->next) {
        if (l->timeout == timeout)
            return TRUE;
    }
    l = dbus_new(struct timeoutlist_t, 1);
    if (NULL == l)
        return FALSE;
    l->timeout = timeout;
    fprintf(stdout, "timeoutAdd:%s\n", _verbose_timeout(timeout));
    l->next = timeoutlist;
    timeoutlist = l;
    return TRUE;
}
void timeoutRemoveNotify(DBusTimeout *timeout, void *data) {
    struct timeoutlist_t *pre = NULL, *l = timeoutlist;
    while( l != NULL ) {
        if (l->timeout == timeout) {
            if (pre == NULL)
                timeoutlist = l->next;
            else
                pre->next = l->next;
            fprintf(stdout, "timeoutRemove:%s\n",
                    _verbose_timeout(timeout));
            break;
        }
        pre = l;
        l = l->next;
    }
}
void timeoutToggleNotify(DBusTimeout *timeout, void *data) {
    fprintf(stdout, "timeoutToggle: %s\n", _verbose_timeout(timeout));
}
/**
* in this function, we call dbus_timeout_handle to handle all timeout
* events, it will call internal predefined handler to process.
*/
void timeoutHandle() {
    struct timeoutlist_t *l = timeoutlist;
    for (; l != NULL; l = l->next) {
        if (dbus_timeout_get_enabled(l->timeout)) {
            printf("timeoutHandle: %s\n", _verbose_timeout(l->timeout));
            dbus_timeout_handle(l->timeout);
        }
    }
}
/**----------- all handlers -------------------------------------*/
/**
* filter messages that already stayed in the incoming queue,
* decide whether a further process is needed.
*/
DBusHandlerResult msgFilter(DBusConnection *conn,
        DBusMessage *msg, void *data) {
    printf("incoming msg: %s\n", _verbose_message(msg));
    switch (dbus_message_get_type(msg)) {
    case DBUS_MESSAGE_TYPE_METHOD_CALL:
        if (!strcmp(dbus_message_get_member(msg), "ignore")) {
            DBusMessage *errMsg;
            errMsg = dbus_message_new_error(msg,
                    "com.redflag.csy.IgnoreService",
                    "this demonstrate the filter.");
            dbus_connection_send(conn, errMsg, NULL);
            return DBUS_HANDLER_RESULT_HANDLED;
        } else
            break;
    case DBUS_MESSAGE_TYPE_METHOD_RETURN:
        // never reach here.
        break;
    case DBUS_MESSAGE_TYPE_SIGNAL:
        break;
    case DBUS_MESSAGE_TYPE_ERROR:
        break;
    }
    // set this flag is very important, if not, dbus may not
    // process messages for you. it pass the control to dbus
    // default filter.
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
DBusHandlerResult subsection_com_handler(DBusConnection* conn,
        DBusMessage* msg, void* data) {
    if ( strncmp(dbus_message_get_path(msg), objectPaths[1],
                strlen(objectPaths[1])) != 0 ) {
        printf("subsection_com_handler: something wrong.\n");
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
    if ( strncmp(dbus_message_get_member(msg), "pseudo", 6) == 0 ) {
        reply_to_method_call(msg, conn);
        printf("subsection_com_handler: handled.\n");
        return DBUS_HANDLER_RESULT_HANDLED;
    } else
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
DBusHandlerResult subsection_org_freedesktop_handler(
        DBusConnection* conn, DBusMessage* msg, void* data) {
    if ( strncmp(dbus_message_get_path(msg), objectPaths[0],
                strlen(objectPaths[0])) != 0 ) {
        printf("subsection_org_freedesktop__handler: something wrong.\n");
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
   
    if ( strncmp(dbus_message_get_member(msg), "error", 5) == 0 ) {
        printf("subsection_org_freedesktop_handler(error): handled.\n");
        return DBUS_HANDLER_RESULT_HANDLED;
    } else
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
DBusHandlerResult subsection_com_redflag_handler(DBusConnection* conn,
        DBusMessage* msg, void* data) {
    if ( strncmp(dbus_message_get_path(msg), objectPaths[2],
                strlen(objectPaths[2])) != 0 ) {
        printf("subsection_com_redflag_handler: something wrong.\n");
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
   
    if ( strncmp(dbus_message_get_member(msg), "pseudo", 6) == 0 ) {
        reply_to_method_call(msg, conn);
        printf("subsection_com_redflag_handler: handled.\n");
        return DBUS_HANDLER_RESULT_HANDLED;
    } else
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
DBusHandlerResult object_dbus_handler(DBusConnection* conn,
        DBusMessage* msg, void* data) {
    if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
        printf("object_dbus_handler: method_return.\n");
        int ret = handle_method_return( conn, msg );
        printf("object_dbus_handler: handled.\n");
        return ret;
    }
    fprintf(stdout, "object_dbus_handler: cannot handle.\n");
    fprintf(stdout, "\t%s\n", _verbose_message(msg));
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
DBusHandlerResult object_csy_handler(DBusConnection* conn,
        DBusMessage* msg, void* data) {
    switch (dbus_message_get_type(msg)) {
    case DBUS_MESSAGE_TYPE_SIGNAL:
    case DBUS_MESSAGE_TYPE_ERROR:
        fprintf(stdout, "object_csy_handler(error/signal):\n\t%s\n",
                _verbose_message(msg));
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    case DBUS_MESSAGE_TYPE_METHOD_RETURN:
            return handle_method_return(conn, msg);
    case DBUS_MESSAGE_TYPE_METHOD_CALL:
        if ( strstr(dbus_message_get_path(msg), "sycao") != NULL ) {
            printf("object_csy_handler(call): "
                    "cannot handle, through to tree.");
            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
        }
        if ( !strcmp(dbus_message_get_member(msg), "pseudo") ) {
            reply_to_method_call(msg, conn);
            printf("object_csy_handler(call): handled.\n");
            return DBUS_HANDLER_RESULT_HANDLED;
        } else {
            fprintf(stdout, "object_csy_handler(call): cannot handle.\n");
            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
        }
    }
}
void object_unregister_handler(DBusConnection* conn, void* data) {
    printf("object_unregister_handler:\n");
}
/**----------------testing routines ----------------------------------*/
/**
* test send signal method call, send certain signal randomly
*/
void send_random_signal(DBusConnection *conn) {
    DBusMessage *msg;
    char *msg_args[] = {
        "/org/freedesktop/DBus\0org.freedesktop.DBus\0NotExist",
        "/org/freedesktop/HAL\0org.freedesktop.HAL\0Device",
        "/com/redflag/csy\0com.redflag.csy\0NewSignal",
        "\0", "\0"
    };
    char *pargs = msg_args[rand()%(sizeof(msg_args)/sizeof(msg_args[0]))];
    if (!*pargs)
        return;
   
    long chance = 0x0fffffff;
    if ((rand() % chance) > 100)
        return;
    char *path = pargs;
    char *intf, *member;
    while( *pargs++ );
        intf = pargs;
    while( *pargs++ );
        member = pargs;
//    printf("%s,%s,%s\n", path, intf, member);
    dbus_uint32_t serial;
    msg = dbus_message_new_signal(path, intf, member);
    if (NULL == msg)
        err_quit("no enough memory.\n");
    if ( !dbus_connection_send(conn, msg, &serial) )
        fprintf( stderr, "no enough memory to send signal.\n" );
    fprintf(stdout, "SendSignal(%d): %s\n", serial, _verbose_message(msg));
}
void pendingCallNotify(DBusPendingCall *pending, void *data) {
    // one can process the pending and then read the reply message
    // alternatively, one can leave it to message handlers
    DBusMessage *msg = dbus_pending_call_steal_reply(pending);   
    printf( "pendingCallNotify: %s\n", _verbose_message(msg) );
}
void send_random_method_expecting_reply(DBusConnection *conn) {
    DBusMessage *msg;
    char *msg_args[] = {
        "org.freedesktop.DBus\0/org/freedesktop/DBus\0org.freedesktop.DBus\0ListNames",
        "org.freedesktop.DBus\0/org/freedesktop/DBus\0org.freedesktop.DBus.Peer\0Ping",
        "org.freedesktop.DBus\0/org/freedesktop/DBus\0org.freedesktop.DBus.Peer\0GetMachineId",
        "\0" // add possibility of not sending any msg this turn
    };
    long chance = 0x0fffffff;
    if ((rand() % chance) > 100)
        return;
    char *pargs = msg_args[rand()%(sizeof(msg_args)/sizeof(msg_args[0]))];
    if (!*pargs)
        return;   
    char *dest, *path, *intf, *member;
    dest = pargs;
    while( *pargs++ );
        path = pargs;
    while( *pargs++ );
        intf = pargs;
    while( *pargs++ );
        member = pargs;
   
    msg = dbus_message_new_method_call(dest, path, intf, member);
    if (NULL == msg)
        err_quit("no enough memory.\n");
    DBusPendingCall *pendingCall;
    if (!dbus_connection_send_with_reply(conn, msg, &pendingCall, 10000))
        fprintf( stderr, "no memory to send method_call.\n" );
    dbus_pending_call_set_notify(pendingCall, pendingCallNotify, NULL, NULL);
    struct seriallist_t *l;
    l = dbus_new(struct seriallist_t, 1);
    l->serial = dbus_message_get_serial(msg);
    l->next = seriallist;
    seriallist = l;
    fprintf(stdout, "SendMethod: %s\n", _verbose_message(msg));
}
int dbus_process_msg_loop() {
    DBusConnection *conn;
    DBusError *perr;
    // 1. connection to bus
    perr = dbus_new(DBusError, 1);
    dbus_error_init(perr);
    conn = dbus_bus_get(DBUS_BUS_SESSION, perr);
    if ( dbus_error_is_set(perr) )
        err_quit("connection failed.\n");
    // 2. request well-known name
    int ret = dbus_bus_request_name( conn, "com.redflag.csy",
            DBUS_NAME_FLAG_REPLACE_EXISTING, perr );
    if ( DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret )
        err_quit("not primary owner.\n");
    // 3. register any handler
    dbus_bus_add_match( conn,
            "type='method_call',path='/org/freedesktop/csy'"
            ",interface='com.redflag.sycao',member='faked'"
            , perr);
    if (dbus_error_is_set(perr))
        err_quit("add match failed.\n");
    dbus_connection_add_filter( conn, msgFilter, NULL, NULL);
    dbus_connection_set_watch_functions( conn,
            watchAddNotify, watchRemoveNotify, watchToggleNotify,
            NULL, NULL );
    dbus_connection_set_timeout_functions( conn,
            timeoutAddNotify, timeoutRemoveNotify, timeoutToggleNotify,
            NULL, NULL );
    int i;
    for (i = 0; i  sizeof(objectPaths)/sizeof(objectPaths[0]); i++) {
        if (i  3)
            dbus_connection_register_fallback( conn,
                    objectPaths, &objectPathVTable, NULL );
        else
            dbus_connection_register_object_path( conn,
                    objectPaths, &objectPathVTable, NULL);
    }
    printf("main: registered %d objectHandlers.\n", i);
    // 4. main loop: wait and process incoming msgs
    // there are several steps :
    //    a. check if any watch is ready for read (incoming data prepared)
    //    or write (outgoing data prepared), and process it.
    //    b. check if any timeout occurred ( i know now method_call that
    //    needs a reply will set a timeout ), and process it.
    //    c. call dispatch will do:
    //        i. parse incoming raw data if has.
    //        ii. process any pending call ( bind with a reply message );
    //        ii. call any filter registered.
    //        iv. call any object path handler registered.
    //        ps: a single dispatch call processes at most one message.
    //            
    while( 1 ) {
        DBusDispatchStatus status;
        
        while( 1 ) {
            watchHandler();
            timeoutHandle();
            status = dbus_connection_dispatch( conn );
            
            switch( status ) {
            case DBUS_DISPATCH_DATA_REMAINS:
                // there may be more msgs remain, go on
                printf("DBUS_DISPATCH_DATA_REMAINS\n");
                continue;
            case DBUS_DISPATCH_COMPLETE:
                break;
            case DBUS_DISPATCH_NEED_MEMORY:
                fprintf( stderr, "more memory is needed.\n" );
            }
            break;
        }
        send_random_signal( conn );
        send_random_method_expecting_reply( conn );
}
    dbus_free(perr);
    return 0;
}
int main(int argc, char **argv) {
    return dbus_process_msg_loop();
}
编译的时候带上:
CFLAGS=-Wall `pkg-config dbus-1 --cflags`
LDFLAGS=`pkg-config dbus-1 --libs`
可以用下面的脚本测试下不同的对象路径、接口和方法的影响:
#!/bin/bash
if [ $# == 0 ]; then
        loop=10
else
        loop=$1
fi
loop2=$loop
loop3=$loop
loop4=$loop
while (( loop > 0 ))
do
        dbus-send --session --type="method_call" --print-reply  --dest="com.redflag.csy" "/com/redflag/csy" "com.redflag.csy.pseudo" int32:$loop string:"hello,dbus1"
        let "loop = loop - 1"
done
while (( loop2 > 0 ))
do
        dbus-send --session --type="method_call" --print-reply  --dest="com.redflag.csy" "/org/freedesktop/csy" "com.redflag.csy.pseudo" int32:$loop2 string:"hello,dbus2"
        let "loop2 = loop2 - 1"
done
while (( loop3 > 0 ))
do
        dbus-send --session --type="method_call" --print-reply  --dest="com.redflag.csy" "/com/redflag/sycao" "com.redflag.sycao.pseudo" int32:$loop3 string:"hello,dbus3"
        let "loop3 = loop3 - 1"
done
while (( loop4 > 0 ))
do
        dbus-send --session --type="method_call" --print-reply  --dest="com.redflag.csy" "/org/freedesktop/csy" "com.redflag.csy.ignore" int32:$loop4 string:"hello,dbus4"
        let "loop4 = loop4 - 1"
done

shell

在当前目录下的寻找包含"socket"的.c, .h文件

 grep socket `find . -name *.[ch]` | awk -F":" '{print $1}' | uniq