# 基于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启动参数配置如下: ```shell -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等内容,数字越小越优先执行。 ```shell #! /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调用相反的操作,注意顺序也正好相反。 ```shell 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 ```shell #!/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 ```shell #!/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 ```shell # 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 ```shell #!/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 ```shell #!/bin/sh # udhcpc script edited by Tim Riker [ -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](https://www.netfilter.org/projects/libmnl/)是ethtool工具的依赖,编译动态库,完成后根据需求拷贝输出到目标系统内。 ```shell ./configure --host=riscv64-linux-gnu --prefix=$SHELL_FOLDER/output CXX=$CROSS_PREFIX-g++ CC=$CROSS_PREFIX-gcc make -j16 make install ``` #### ethtool编译 [ethtool](https://mirrors.edge.kernel.org/pub/software/network/ethtool/)工具编译,设置好libmnl库路径即可,完成后根据需求拷贝输出到目标系统内。 ```shell ./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](https://www.openssl.org/)库被很多工具依赖,交叉编译如下,完成后根据需求拷贝输出到目标系统内。 ```shell ./Configure linux-generic64 no-asm --prefix=$SHELL_FOLDER/output --cross-compile-prefix=$CROSS_PREFIX- make -j16 make install_sw ``` #### iperf编译 [iperf](https://iperf.fr/)是一个网络性能测试实用工具,设置好openssl库路径即可,完成后根据需求拷贝输出到目标系统内。 ```shell ./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 ``` > 本教程的
github仓库:https://github.com/QQxiaoming/quard_star_tutorial
gitee仓库:https://gitee.com/QQxiaoming/quard_star_tutorial
本节所在tag:ch24