基于qemu-riscv从0开始构建嵌入式linux系统ch24. qemu网卡/linux内核网络配置
virtio-net-device
本节我们给系统添加网络相关的配置,和之前一样virtio-mmio还提供了网络设备的注册,这里我们选择添加qemu支持的最简单的user模式网络,其他博客中有大量介绍使用tap网桥等方式虚拟化的标准网络设备,但是在现在大家多使用笔记本开发,无线网卡往往都不支持虚拟网桥,因此免配置的user模式的虚拟网络是比较简单易用的,注意其存在两个限制即可
- 目标机只支持tcp、udp协议,而不支持icmp等其他协议,因此无法使用ping
- 入站流量是需要在启动qemu前就配置端口转发的,比如将目标机器的22和80端口分别转发到3522和3580,此时主机访问localhost的3522和3580端口就可以访问到目标机内的服务了。
qemu启动参数配置如下:
-netdev user,id=net0,net=192.168.31.0/24,dhcpstart=192.168.31.100,hostfwd=tcp::3522-:22,hostfwd=tcp::3580-:80
-device virtio-net-device,netdev=net0
配置开机启动dhcp
inittab
再次修改inittab脚本如下,原因是我们需要shutdown时执行/etc/init.d/rcK脚本,这样才能正确的关闭一些开机在rcS中启动服务。
::sysinit:/etc/init.d/rcS
console::respawn:/sbin/getty 38400 console
::ctrlaltdel:/sbin/reboot
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
rcS
rcS中添加根据脚本名称依次执行/etc/init.d/S??*系列脚本的初始化代码,例如S01syslogd、S40network等内容,数字越小越优先执行。
#! /bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib
export PATH LD_LIBRARY_PATH
mount -a
/sbin/mdev -s
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mount -t 9p -o trans=virtio,version=9p2000.L,msize=16384 hostshare /mnt/
/bin/hostname -F /etc/hostname
dmesg -n 1
chmod 666 /dev/null
for i in /etc/init.d/S??* ;do
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
$i start
;;
esac
done
echo "---------------------------------------------"
echo " Welcome debugging on Qemu Quard Star board! "
echo "---------------------------------------------"
rcK
rcK调用相反的操作,注意顺序也正好相反。
for i in $(ls -r /etc/init.d/S??*) ;do
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
(
trap - INT QUIT TSTP
set stop
. $i
)
;;
*)
$i stop
;;
esac
done
S40network
S40network比较简单,启动和停止分别调用ifup和ifdown
#!/bin/bash
mkdir -p /run/network
case "$1" in
start)
echo "Starting network... "
/sbin/ifup -a
[ $? = 0 ] && echo "OK" || echo "FAIL"
;;
stop)
echo "Stopping network... "
/sbin/ifdown -a
[ $? = 0 ] && echo "OK" || echo "FAIL"
;;
restart|reload)
"$0" stop
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
ifup与ifdown相关的配置
与ifup和idown相关的配置文件路径如下,这些默认路径的加载位置都是硬编码在相关程序源码中,这里就不在展示busybox的源码了。
- /etc/network/if-pre-up.d/wait_iface
#!/bin/sh
if [ "${IF_WAIT_DELAY}" -a ! -e "/sys/class/net/${IFACE}" ]; then
printf "Waiting for interface %s to appear" "${IFACE}"
while [ ${IF_WAIT_DELAY} -gt 0 ]; do
if [ -e "/sys/class/net/${IFACE}" ]; then
printf "\n"
exit 0
fi
sleep 1
printf "."
: $((IF_WAIT_DELAY -= 1))
done
printf " timeout!\n"
exit 1
fi
- /etc/network/interfaces
# interface file auto-generated by buildroot
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
pre-up /etc/network/nfs_check
wait-delay 15
hostname $(hostname)
- /etc/network/nfs_check
#!/bin/sh
nfsip=`sed -n '/^[^ ]*:.* \/ nfs.*[ ,]addr=\([0-9.]\+\).*/s//\1/p' /proc/mounts`
if [ -n "$nfsip" ] && ip route get to "$nfsip" | grep -q "dev $IFACE"; then
echo Skipping $IFACE, used for NFS from $nfsip
exit 1
fi
在busybox-1.33.1/networking/ifupdown.c里,会自动搜索系统中相关的dhcpc的工具来进行dhcp服务,在busybox中会找到udhcpc,此时便会进一步打开一个守护进程udhcpc,那么相关配置脚本要写在/usr/share/udhcpc/default.script路径内,可以在busybox的CONFIG_UDHCPC_DEFAULT_SCRIPT选项看到,这里使用busybox提供example配置。
- /usr/share/udhcpc/default.script
#!/bin/sh
# udhcpc script edited by Tim Riker <Tim@Rikers.org>
[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
RESOLV_CONF="/etc/resolv.conf"
[ -e $RESOLV_CONF ] || touch $RESOLV_CONF
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] && NETMASK="netmask $subnet"
# Handle stateful DHCPv6 like DHCPv4
[ -n "$ipv6" ] && ip="$ipv6/128"
if [ -z "${IF_WAIT_DELAY}" ]; then
IF_WAIT_DELAY=10
fi
wait_for_ipv6_default_route() {
printf "Waiting for IPv6 default route to appear"
while [ $IF_WAIT_DELAY -gt 0 ]; do
if ip -6 route list | grep -q default; then
printf "\n"
return
fi
sleep 1
printf "."
: $((IF_WAIT_DELAY -= 1))
done
printf " timeout!\n"
}
case "$1" in
deconfig)
/sbin/ifconfig $interface up
/sbin/ifconfig $interface 0.0.0.0
# drop info from this interface
# resolv.conf may be a symlink to /tmp/, so take care
TMPFILE=$(mktemp)
grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
cat $TMPFILE > $RESOLV_CONF
rm -f $TMPFILE
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
fi
;;
leasefail|nak)
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -c $interface || /usr/sbin/avahi-autoipd -wD $interface --no-chroot
fi
;;
renew|bound)
if [ -x /usr/sbin/avahi-autoipd ]; then
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
fi
/sbin/ifconfig $interface $ip $BROADCAST $NETMASK
if [ -n "$ipv6" ] ; then
wait_for_ipv6_default_route
fi
# RFC3442: If the DHCP server returns both a Classless
# Static Routes option and a Router option, the DHCP
# client MUST ignore the Router option.
if [ -n "$staticroutes" ]; then
echo "deleting routers"
route -n | while read dest gw mask flags metric ref use iface; do
[ "$iface" != "$interface" -o "$gw" = "0.0.0.0" ] || \
route del -net "$dest" netmask "$mask" gw "$gw" dev "$interface"
done
# format: dest1/mask gw1 ... destn/mask gwn
set -- $staticroutes
while [ -n "$1" -a -n "$2" ]; do
route add -net "$1" gw "$2" dev "$interface"
shift 2
done
elif [ -n "$router" ] ; then
echo "deleting routers"
while route del default gw 0.0.0.0 dev $interface 2> /dev/null; do
:
done
for i in $router ; do
route add default gw $i dev $interface
done
fi
# drop info from this interface
# resolv.conf may be a symlink to /tmp/, so take care
TMPFILE=$(mktemp)
grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
cat $TMPFILE > $RESOLV_CONF
rm -f $TMPFILE
# prefer rfc3397 domain search list (option 119) if available
if [ -n "$search" ]; then
search_list=$search
elif [ -n "$domain" ]; then
search_list=$domain
fi
[ -n "$search_list" ] &&
echo "search $search_list # $interface" >> $RESOLV_CONF
for i in $dns ; do
echo adding dns $i
echo "nameserver $i # $interface" >> $RESOLV_CONF
done
;;
esac
HOOK_DIR="$0.d"
for hook in "${HOOK_DIR}/"*; do
[ -f "${hook}" -a -x "${hook}" ] || continue
"${hook}" "${@}"
done
exit 0
完成了以上配置,在系统启动时就能看到如下log输出
Starting network...
udhcpc: started, v1.33.1
udhcpc: sending discover
udhcpc: sending select for 192.168.31.100
udhcpc: lease of 192.168.31.100 obtained, lease time 86400
deleting routers
adding dns 192.168.31.3
OK
Starting sshd daemon.
切记如果关闭qemu时需要使用halt命令来关闭网络服务,否则可能下一次启动qemu无法成功获取的到ip地址,在真实开发板应该不会存在该问题,但保持良好的习惯会比较好。
网络相关的工具
既然有了网络,我们可以编译一些网络相关的工具测试系统网络,交叉编译的内容之前已经讲过很多了,这里不在赘述了。
libmnl编译
libmnl是ethtool工具的依赖,编译动态库,完成后根据需求拷贝输出到目标系统内。
./configure --host=riscv64-linux-gnu --prefix=$SHELL_FOLDER/output CXX=$CROSS_PREFIX-g++ CC=$CROSS_PREFIX-gcc
make -j16
make install
ethtool编译
ethtool工具编译,设置好libmnl库路径即可,完成后根据需求拷贝输出到目标系统内。
./configure --host=riscv64-linux-gnu --prefix=$SHELL_FOLDER/output MNL_CFLAGS=-I$SHELL_FOLDER/output/include MNL_LIBS="-L$SHELL_FOLDER/output/lib -lmnl" CXX=$CROSS_PREFIX-g++ CC=$CROSS_PREFIX-gcc
make -j16
make install
openssl编译
openssl库被很多工具依赖,交叉编译如下,完成后根据需求拷贝输出到目标系统内。
./Configure linux-generic64 no-asm --prefix=$SHELL_FOLDER/output --cross-compile-prefix=$CROSS_PREFIX-
make -j16
make install_sw
iperf编译
iperf是一个网络性能测试实用工具,设置好openssl库路径即可,完成后根据需求拷贝输出到目标系统内。
./configure --host=riscv64-linux-gnu --prefix=$SHELL_FOLDER/output --with-openssl=$SHELL_FOLDER/output CXX=$CROSS_PREFIX-g++ CC=$CROSS_PREFIX-gcc
make -j16
make install