Archive for the ‘嵌入式Linux’ Category
Linux的精灵(daemon)进程和僵尸(zombie)进程
有些日子没有跟工程师一起工作了。昨天星期五,我回到川崎的办公室和同事一起结果一个客户反馈的问题。这个工作却又让我怀念以前作研发的那些时光。突然觉得有些东西还是不应该被忘记,就写在这里作笔记了。
守护进程(Daemon)
守护进程——我情愿把他叫做精灵进程——是多种操作系统中的一种常驻程序。常驻的意思是,在系统运行期间,这些进程都一直存在。很明显,大多数服务器程序都是精灵进程。在这里我要说一说自己知道关于精灵进程的方法,和与此相关的一些技巧。
如何产生一个精灵进程?
Linux系统启动以后,它通常启动的第一个进程就是init进程。init进程的进程ID是1,并且它会一直在系统运行期间存续。同时,Linux对进程的管理策略有两个比较特殊的地方,其中之一就是,当一个进程的父进程结束,它的子进程如果还继续运行,那么,子进程就会被init进程收养。也算就是说,Linux对进程的管理策略,其实是一直在努力维护一棵树。利用这一点,就可产生伴随init一直存在的精灵进程。代码例子如下:
/* file: daemon_sample.c */
#include
#include
#include
#include
int main ()
{
pid_t pid,sid;
/* fork to leave parent */
pid = fork ();
if (pid < 0) {
printf ("failed to fork!\n");
exit (EXIT_FAILURE);
} else if (pid > 0) {
/* parent process go to exit */
exit (EXIT_SUCCESS);
}
/* child process */
chdir("/");
sid = setsid ();
if (sid < 0) {
printf ("Failed to set session id.\n");
exit (EXIT_FAILURE);
}
/* ensure all files created by itself are accessible */
umask (0);
printf ("Daemon adopted by parent %d\n", getppid());
/* daemon loop */
while (1) {
sleep (1);
}
}
产生一个精灵进程的过程需要有一下几步:
- 从启动该程序的进程中fork出来一个子进程;如果子进程产生成功,那么就将父进程结束
- 子进程需要作一些处理来保证自己作为精灵的地位;改变当前工作目录;利用setsid系统调用重新设置一个session;利用umask系统调用消除父进程对于文件控制的影响,从而保证精灵可以正确的访问它自己生成的各种文件——其中比较重要的是IPC
- 子进程需要启动一个自身处理的循环
这是一个非常简单的例子,通常情况下,子进程还需要关闭标准输入输出等等工作。与此相关的详细讨论可以查看
http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html
这里有一个可以下载到本地的PDF版本
http://www.google.co.jp/url?sa=t&source=web&ct=res&cd=2&url=http%3A%2F%2Fwww.cs.aau.dk%2F~adavid%2Fteaching%2FMTP-05%2Fexercises%2F10%2FLinux-Daemon_Writting.pdf&ei=W7pFSreDHoTW7APC0_Eh&usg=AFQjCNHyZnXmdKL8ebxJ8UXQsYeGHiqRwQ&sig2=7Of2PvUnmTm8xqZifI-aAw
怎样判断一个进程是否是精灵进程?
恩,奇怪的地方就在与此了。如果用如下命令编译上面的简短程序,你会发现一处不太符合想象的地方。
$ gcc -o daemon_sample daemon_sample.c
在终端窗口中运行这个程序,然后我们作一些观察。
$ ./daemon_sample
按照上面我叙述过的内容,这个程序的将被init进程收养。那么,理所当然的它的父进程ID应该是“1”。然而,这个程序运行时却抛出了这样一句话。
Daemon adopted by parent 29914
这里的29914是通过getppid()取得的,难道有什么不对头?为了确认情况,再用ps查看一下究竟发生了什么事情。
$ ps -e -o pid,ppid,cmd,tty | grep daemon_sample
29915 1 ./daemon_sample ?
利用ps命令的-o选项,可以查看进程的pid,ppid,命令行和运行终端的信息。显而易见,这个精灵进程事实上的ppid是“1”。那么,为什么getppid()不能返回正确的值?难道这里是个BUG?就目前Linux内核的状况来说,这不是一个BUG,查看getppid的手册页可以看到,getppid并不保证能够取得一个进程被收养以后的父进程ID。或者有兴趣的xdjm可以尝试一下将这个功能升级一下:)。
换句话说,不能使用getppid来判断一个进程时候是精灵进程。那么,应该怎样判断?
再看上面ps的输出结果,你会发现这个进程的tty域是一个问号。这表明,该进程并没有被attach到任何终端设备上去。事实上所有的精灵进程都有这个特性。利用ps做一个验证吧。
$ ps -e -o pid,ppid,cmd,tty | grep daemon
898 1 /sbin/udevd --daemon ?
2464 1 /bin/dbus-daemon --system ?
3305 1 avahi-daemon: running [capr ?
3829 1 //bin/dbus-daemon --fork -- ?
29915 1 ./daemon_sample ?
31584 29871 grep --color=auto daemon pts/1
这里有ps命令的结果,它被attach到伪终端pts/1。所以,可以利用这个特性来判断一个进程是否是精灵进程。
if ((devtty = open ("/dev/tty", O_RDWD)) < 0) {
printf ("This is a daemon\n");
} else {
printf ("This is not a daemon\n");
}
在UNIX论坛上有一个关于这个话题的旧帖子,地址如下:
http://www.unix.com/high-level-programming/72479-how-find-if-process-daemon.html
精灵进程大致就是这样。
僵尸进程
僵尸进程其实是个错误。
这个错误产生的原因在于,子进程已经结束,但是父进程却没有对它进行清理。僵尸进程会被init收编,并且init会料理这些僵尸的“善后事宜”。我们可以写一个很蹩脚的程序来产生一个货真价实的僵尸进程。僵尸来了!
/* file: zombie.c */
#include
#include
#include
int main ()
{
pid_t child_pid;
/* Create a child process. */
child_pid = fork ();
if (child_pid > 0) {
/* This is the parent process. Sleep for a minitue. */
sleep (60);
} else {
/* This is the child process. Exit immediately. */
exit (EXIT_SUCCESS);
}
return 0;
}
编译并运行这个程序,在程序运行期间,利用ps查看它的状态,你会得到下面的结果:
$ ps -e -o pid,stat,cmd | grep zombie
4302 S+ ./zombie
4303 Z+ [zombie]
你会发现进程4302——也就是僵尸的父进程——还没有结束,而进程4303进程的状态已经变成了“Z”。这也就意味这该程序产生的子进程已经变成了僵尸。
在一般的桌面系统上,即使产生僵尸进程也不是什么大不了的事情,因为,系统资源并不紧张的情况下,僵尸最终会被init清理。但是,在高负载的服务器或者资源非常有限的嵌入式系统中如果大量出现僵尸,那会是个一个麻烦。所以,避免产生僵尸进程的方法就是在父进程中正确的处理wait和SIGCHLD信号,具体做法google一下可以出很多结果,这里就不再累述了。
嘿~,如果您喜欢我的博客,您可以通过RSS.链接将本博客的最新文章传输到您喜欢的阅读器。
- Tags: 嵌入式Linux
- Posted in Linux tips, 嵌入式Linux
- No Comments
打造嵌入式软件开发团队(3) 使用minicom做串口连接
minicom的初始配置
起动minicom
在终端中敲minicom,然后回车……(画外音:老兄,这个地球人都知道吧)。
$ minicom
如果你非常unlucky,发行版的默认安装中居然没有包含这个简单易用的串口连接工具,那么,找apt-get帮忙吧。
$ apt-cache search minicom
$ sudo apt-get install minicom
这里需要注意的问题是,如果你的locale设置的是中文,那么minicom打开后,那些边边角角条条框框会不能对齐,这基本山是因为程序本身不能计算双字节字符在屏幕上的正确宽度造成的,如果你英文好一些,并且不愿忍受破坏视觉的痛苦,推荐使用下面的命令启动minicom。
$ LC_ALL=C minicom
为了以后方便一些,就直接把这个命令写入$HOME/bin/里面,并且将这个路径添加进入PATH变量最前面的位置用以替换默认的minicom启动程序。
$ echo "#!/bin/sh LC_ALL=C minicom" > $HOME/bin/minicom
$ chmod +x $HOME/bin/minicom
如果minicom是第一次启动,打开之后就会遇到提示,可能有两种情况
- minicom说普通用户不能打开设备文件/dev/modem之类,如果你确信你需要使用这个设备进行通信,那么请用root用户或者sudo命令将这个设备文件更改访问属性;
- minicom说找不到默认的配置文件
恩,我们要说的就是如何给他配置起来,接着看吧。
打开配置菜单
第一次启动minicom可以使用root身份打开,并且加入-s选项,这是为了方便配置。
$ LC_ALL=C sudo minicom -s
这时回打开一个对话菜单,它长的有点像下面这个样子。
+-----[configuration]------+
| Filenames and paths |
| File transfer protocols |
| Serial port setup |
| Modem and dialing |
| Screen and keyboard |
| Save setup as dfl |
| Save setup as.. |
| Exit |
+--------------------------+
用上下键选择Serial port setup,选择进入。
+-----------------------------------------------------------------------+
| A - Serial Device : /dev/ttyS0 |
| B - Lockfile Location : /var/lock |
| C - Callin Program : |
| D - Callout Program : |
| E - Bps/Par/Bits : 115200 8N1 |
| F - Hardware Flow Control : No |
| G - Software Flow Control : No |
| |
| Change which setting? |
+-----------------------------------------------------------------------+
将串口的各个配置值设置成你需要的样子。按每个选项前面的字母键进入配置选单。其中,Serial Device需要指定成你实际使用的串口设备的设备文件;Lockfile Location使用默认就可以了,这个文件使用来锁定设备的,以保证不会设备被另外一个程序同时使用;Callin/out Program略去,嵌入式软件开发的时候大多不需要这个,这选项大概是留给调制解调器用的,我也不晓得;Bps/Par/Bits用来配置你的串口的通讯速度,这里我的设备选择了115200 8N1的选项,跟据你的设备选择相应的值;Hardware/Software Flow Control是RS232协议的一部分,用以控制两个设备之间消息传递的流控制——这个玩意儿一两句讲不清楚,有兴趣的话看RS232 flow control and handshaking——这个例子里选择NO。
不同的RS232连接线或者开发版的特殊设计都会导致你需要的配置与例子所说的不同,所以,还是查看一下硬件的连接方式手册再进行配置比较好。
配置完成后,在配置菜单中选择Save setup as dfl,这样就可以将配置结果保存成默认设置。
测试
如果你的开发板中已经烧入了相应的固件支持串口通信的话(一般都会有),这个时候可以给开发版加电测试一下通信状况了。如果配置没有出错,那么就应该有相应的内容表示。
值得一提的是折行控制。如果不进行选择的话,minicom提供的终端是不会折行的,也就是说你输入终端的内容在表示的时候会被短截,为了让自己看得清楚一些,可以用CTRL+A W来进行切换。
退出
在minicom的画面中按CTRL+A,接着按X键。或者CTRL+A Q。两者的区别是X会重置modem,而Q不会重置,并且Q选项需要用户确认。
此后就无须多言了,串口连接只是提供一种连接方式,连接以后根据开发板给公给你的界面,该干嘛干嘛就好了。
–
参考URL:
http://www.interface.co.jp/cpu/sh240_howto/howto06.asp
http://tldp.org/HOWTO/Serial-HOWTO.html
嘿~,如果您喜欢我的博客,您可以通过RSS.链接将本博客的最新文章传输到您喜欢的阅读器。
打造嵌入式软件开发团队(2) tftp服务器
上回书说到嵌入式开发环境的根文件系统如何通过NFS挂载,接下来说一下内核如何通过tftp服务器调试和加载。
Bootloader
通过tftp加载内核的前提是,开发使用的bootloader支持tftp功能。这样的bootloader有很多,在MIPS结构(MIPS4K,BRCM,龙芯等处理器)上,可以尝试使用U-Boot或者CFE。其中U-Boot适用于多种平台,包括ARM,PowerPC,MIPS等多中体系结构,而CFE是Broadcom公司开发的一个开放源代码BL,被广泛应用于MIPS结构。
安装和配置TFTP服务器
在Debian base的Ubuntu系统上安装tftp的方法非常简单。首先,你可以尝试用apt-cache命令查看一下发行版上都提供了哪些tftp相关的软件可以使用。
$ apt-cache search tftp
...
tftp-hpa - HPA's tftp client
tftpd-hpa - HPA's tftp server
atftp - advanced TFTP client
atftpd - advanced TFTP server
...
tftp - Trivial file transfer protocol client
tftpd - Trivial file transfer protocol server
atftp和tftp-hpa都是比较特殊的版本,所以,为了简便起见,这个介绍中只安装通用的tftp和tftpd。你可以使用下面的命令安装TFTP。
$ sudo apt-get install tftpd tftp
确认inetd.conf中是否正确安装了tftpd。
grep tftp /etc/inetd.conf
准备TFTP服务器使用的目录,可以在这个目录中放入需要共享的文件,比如编译好的内核image文件。
$ sudo mkdir /tftpboot
$ sudo chmod 777 /tftpboot
一般,为了配置简便,通常将tftp服务的目录放在/tftpboot,当然这个位置是可以配置的,在/etc/inetd.conf文件中,可以看到tftp目录被放在了/srv/tftp中,你可以根据需要更改。
$ sudo sed -e "s,/src/tftp,/tftpboot," /etc/inetd.conf > \
/etc/inetd.conf_out && \
sudo mv /etc/inetd.conf /etc/inetd.conf_bak && \
sudo mv /etc/inetd.conf_out /etc/inetd.conf
测试一下。
$ echo "HELLO" > /tftpboot/FILE.txt
$ tftp
tftp> connect localhost
tftp> get FILE.txt
另外一个选择是使用xinet.d来驱动tftpd,取代inet.d。
安装xinet.d。
$ sudo apt-get install xinetd
配置tftpd服务。
打开/etc/xinet.d/tftpd文件,根据下面的样子照猫画虎就行了,需要注意的是disable=yes这一行变成disable=no。另外,默认的配置文件中可能需要增加访问控制选项,即将only_from这个选项固定为局域网。
service tftp
{
protocol = udp
port = 69
socket_type = dgram
wait = yes
user = nobody
group = nobody
server = /usr/sbin/in.tftpd
server_args = /tftpboot
only_from = 192.168.1.0/24
disable = no
}
使用xinet.d的好处是安全性增强。也是一个比较不错的选择。
–
参考URL:
http://hogeo.jp/blog/memo/2008/07/tftpubuntu.html
http://0×100.com/Gentoo/Server/tftpd.html
嘿~,如果您喜欢我的博客,您可以通过RSS.链接将本博客的最新文章传输到您喜欢的阅读器。
