基于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