• 266查看
  • 0回复

[Autosar] AUTOSAR入门-SoAd模块和TcpIp模块

[复制链接]


该用户从未签到

发表于 3-3-2024 09:21:27 | 显示全部楼层 |阅读模式

汽车零部件采购、销售通信录       填写你的培训需求,我们帮你找      招募汽车专业培训老师


AUTOSAR入门-SoAd模块和TcpIp模块

AUTOSAR入门-SoAd模块和TcpIp模块w1.jpg

    有一句话叫做“All is in the code”,网上好像没找到是谁说的,算我杜撰的
AUTOSAR入门-SoAd模块和TcpIp模块w2.jpg
。这也是我坚信的一个观点:一切都在代码里。不管你做什么技术,什么业务,只要你跟软件相关有代码,直接看代码就可以反推出来技术是怎么设计实现的。比如一个高境界的C程序员,那么别管什么技术,比如操作系统、各种应用app、驱动、无线/网络协议等,只要是C写的,有代码,就都可以搞,而且可以迅速上手,看海量C代码如喝白水。因为万变不离其宗,都是C语言的逻辑在支撑,正所谓“无招胜有招”。

    按照这个思路,我们先不看SoAd模块和TcpIP模块是什么,直接分析代码来实践下。在AUTOSAR入门-基于以太网诊断实验中,报文交互的流程为:网卡驱动-》网络接口-》网络协议栈-》SoAd模块-》DoIP模块-》PduR模块-》Dcm模块。其中蓝色的部分还没讲,跟网络协议栈相关,本次文章全都给讲了。


    嵌入式网络通信基础

AUTOSAR入门-SoAd模块和TcpIp模块w3.jpg

    提起嵌入式,我的感觉是有一定的门槛的,需要计算机软硬件很多的知识,不像搞java应用,非科班学几个月也可以找工作。大概十年前当时的嵌入式多么的火爆,而今却成了全民AI的互联网,也是让人感慨三十年河东三十年河西。下面是我的一点偏见:那些深度学习AI的东西的确是能产生很大的经济效益,但是在技术方面除了设计算法的大牛,大多数从业者只是调参做标记等的底层劳动力,调得一手好参,但是真正的技术学到了多少,国家被卡脖子的技术又懂得多少,技术壁垒有多高,能算得上多高档次的人才。当互联网被整治(国家不让互联网躺着挣钱,把国家的事干了),这股热潮可能会褪去。有一句狠话:当潮水褪去,才知道谁在裸泳。

    下面扯点我走上嵌入式这条路的过程,记得十几年前大二的时候我电脑上就安装了ubuntu,当时流行里面的四个桌面围成鱼缸养鱼,觉得很炫酷。然后计算机专业学校各种软硬件知识的课都有,杂而不精,对那些硬件理论挺有兴趣,特别是软硬件结合的东西。对界面软件倒是兴趣不大。然后毕业前来到了北京,在清华前面的小民房里面的亚嵌培训班,我还听过一周的课,也没有疫情还可以经常去清华校园逛。一晃很多年,亚嵌的老板老早也出国移民了,后来就成了华清远见的天下。岁月流逝,还是留下来了一些东西,比如这本书:《Linux c一站式编程》http://staff.ustc.edu.cn/~guoyan/os12/LinuxC.pdf

    首先进入Linux c编程,熟悉gcc和makefile。然后主要是一些网络编程的基础知识,这一部分是嵌入式开发的基本功。我们翻到36章 TCP/IP协议基础

AUTOSAR入门-SoAd模块和TcpIp模块w4.jpg

    书上举例的应用层协议是FTP,这里我们替换为我们的DoIP进行分析,DoIP也是一个应用层协议,可以基于TCP进行传输。理论就是上面的这个图,客户端和服务器通过网线或者无线网络进行通信。就像A给B送一封信,但是A写完信后还要弄个信封装起来,然后帖个邮票等操作,然后邮递员把信送到B手里,B还要拆信封,才能看到信的内容。DoIP报文可以看做是信,那么TCP/IP这一套东西就是信封,以太网就是邮政送信的。

AUTOSAR入门-SoAd模块和TcpIp模块w5.jpg

    一条报文可以看成一块内存buffer,里面都是按顺序排列的二进制0和1,和代码里面的结构体是一一对应的,人操作报文的时候是通过代码里面的结构体,机器传输报文的时候是通过二进制的0和1。我们写代码的时候就可以把一个报文看成一个结构体就可以了。我看了下代码,我们as上的客户端和服务器DoIP模块的代码都没有用结构体(编程水平有点low),我修改了下client程序的代码,添加了DoIP报文的结构体(已上库),在as/socket_tool/doip.h中:

AUTOSAR入门-SoAd模块和TcpIp模块w6.jpg

这个data[0]是什么?首先data在这个结构体中不占空间,是一个指针,可以指向另一个报文结构体,这里我们指向UDS报文。这个是报文套报文的一个典型用法,在代码里面解析或者组装报文的时候,就知道它的便利性了。

    接下来看,第37 章socket编程。这个就是上学的时候学的《计算机网络》课程,现在想想那时老师真是太扯了,只讲理论,为啥不讲一下实践,还是说那时水平太菜,老师怕学生实践不了。近来我看清华的一些本科生OS教程,感觉老师真的太重要了,陈渝老师那书直接上代码实践,理论知识根本就是小菜一碟不值得专门讲的,随意就穿插进去了。真是感叹清华老师的底蕴实在太厚了,而很多高校只讲课本理论,是不是因为那老师之前都没见过那门课,自己找个课本学习下就可以教学了,自己都不知道怎么实践的。这样这样看来上个好大学很重要啊,不然上个电子类的大学也是可以的。

    上面扯的有点多,下面就开始Show me the code! 先来看个下面的TCP协议通信流程图,里面直接放了要执行的函数,分为客户端和服务器端,妥妥的操作指导。

AUTOSAR入门-SoAd模块和TcpIp模块w7.jpg

2. Client程序

    Client程序是在Linux下用c语言写的,代码路径为:as/socket_tool/client.c。

这里为什么可以在Linux下写一个程序,为啥不是在AS内部的代码里面实现clinet。这里是因为网络交互是以01二进制报文的形式,通过以太网交互的,而以太网的两端的软件不论什么设备、平台、语言都可以,基于此可以实现万物互联。所谓的分布式软总线,就是这么个概念,不论什么设备什么平台什么语言通过网络接入进来就可以通信。下面具体看c实现的代码:
memset(&sockaddr,0,sizeof(sockaddr));sockaddr.sin_port= htons(port);sockaddr.sin_family= AF_INET;socketfd= socket(AF_INET,SOCK_STREAM,0);//建立socket
//连接服务器inet_pton(AF_INET,servInetAddr,&sockaddr.sin_addr);if((connect(socketfd,(structsockaddr*)&sockaddr,sizeof(sockaddr))) < 0 ){printf("connecterror %s errno: %d\n",strerror(errno),errno);exit(0);}//发送数据send_num =send(socketfd,sendline,send_len,0);
socketfd = socket(AF_INET,SOCK_STREAM,0);是建立一个socket,是建立在tcp/ip协议上的,SOCK_STREAM表示面向字节流是TCP,SOCK_DGRAM表示数据流是UDP。建立socket后,clinet会调用connnect函数连接服务器,sockaddr参数里面回携带服务器的ip和端口。连接成功的话就会调用send函数发送DoIP报文。报文的组装可以自己查看代码。

3. AS网络协议栈初始化和运行

AUTOSAR入门-SoAd模块和TcpIp模块w8.jpg

    再看下这个图,上面client是在linux上发出去的,linux把剩下的事情都包了,然后通过以太网发送给了as,在as里面,第一个程序就是以太网驱动程序,就是我们这里的pci网卡驱动。这个网卡是我们通过qemu添加上去的,见building.py里面代码。

    驱动是在系统启动的时候进行初始化的,下面先看下网卡驱动程序初始化的过程。系统启动后大概会执行下面的过程:TASK(SchM_Startup)-》EcuM_StartupTwo-》EcuM_AL_DriverInitTwo-》SoAd_Init-》TcpIp_Init-》LwIP_Init-》tcpip_init。tcpip_init在as/release/download/lwip开源软件中实现。具体的as里面的os和初始化单独一篇文章再说下。

3.1 SoAd_Init

这里我们关注SoAd_Init函数,代码在

com/as.infrastructure/communication/SoAd/SoAd.c
SocketAdminList.SocketState = SOCKET_INIT;SocketAdminList.SocketConnectionRef = &SoAd_Config.SocketConnection;
SoAd_Config是配置文件,在

com/as.application/common/config/SoAd_Cfg.c中定义
const SoAd_ConfigType SoAd_Config ={   .SocketConnection = SoAd_SocketConnection,    .SocketRoute =SoAd_SocketRoute,   .DoIpTargetAddresses = SoAd_DoIpTargetAddresses,    .DoIpTesters=SoAd_DoIpTesters,   .DoIpRoutingActivations = SoAd_DoIpRoutingActivations,   .DoIpRoutingActivationToTargetAddressMap =SoAd_DoIpRoutingActivationToTargetAddressMap,    .PduRoute =SoAd_PduRoute};staticconst SoAd_SocketConnectionType SoAd_SocketConnection [SOAD_SOCKET_COUNT] ={        {       /* for DCM */               .SocketId= 0,               .SocketLocalIpAddress= "172.18.0.200",               .SocketLocalPort= 13400,               .SocketProtocol= SOAD_SOCKET_PROT_TCP,               .AutosarConnectorType= SOAD_AUTOSAR_CONNECTOR_DOIP,        }, };
可以看到id 172.18.0.200是DoIP使用的,端口号是13400,使用的TCP协议。要修改了可以在这里修改。

接下来就是服务器端socket的类型操作了:

系统启动后TASK(SchM_BswService) 会循环调用

SoAd_MainFunction(void),主要执行scanSockets();进入状态机

初始化的的状态是SOCKET_INIT 执行socketCreate(i);
sockFd = SoAd_CreateSocketImpl(AF_INET, sockType, 0);--》lwip_socket(domain, type,protocol);    --》onn = netconn_new_with_callback(NETCONN_TCP,event_callback);        --》#define netconn_new_with_callback(t, c)netconn_new_with_proto_and_callback(t, 0, c)        --》msg.function = do_newconn;-》pcb_new(msg);#分配pcbSoAd_BindImpl(sockFd,SocketAdminList[sockNr].SocketConnectionRef->SocketLocalPort, SocketAdminList[sockNr].SocketConnectionRef->SocketLocalIpAddress);    --》lwip_ioctl(s, FIONBIO, &on);SoAd_ListenImpl(sockFd, 20) == 0    --》lwip_listen(s, backlog);SocketAdminList[sockNr].SocketState = SOCKET_TCP_LISTENING;
经过设置socket,状态改为监听,会执行socketAccept(i);函数
clientFd =SoAd_AcceptImpl(SocketAdminList[sockNr].SocketHandle, &RemoteIpAddress,&Rem     otePort);    --》lwip_accept(s, (struct sockaddr*)&client_addr, (socklen_t *)&addrlen);if( clientFd != (-1)){    SocketAdminList[sockNr].SocketState= SOCKET_TCP_READY;}
如果有数据则状态改为TCP接收:SOCKET_TCP_READY

没有数据,打印lwip_accept(0): returning EWOULDBLOCK

可以看到上面是server端创建了socket,等待client的连接。

3.2 LwIP_Init

TcpIp_Init函数里面,主要执行了LwIP_Init函数,下面主要看这个函数,位置在

com/as.infrastructure/arch/common/lwip/sys_arch.c
ethernet_configure(); #没找到定义的地方re_sys_init(); #线程个数信息初始化tcpip_init(tcpip_init_done, NULL); #初始化运行lwip的tcpip线程GET_BOOT_IPADDR; #获取网卡的IP地址GET_BOOT_NETMASK;GET_BOOT_GW;ethernet_set_mac_address(macaddress); #设置网卡的mac地址/* Add network interface to the netif_list */netif_add(&netif,&ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);/*  Registers thedefault network interface.*/netif_set_default(&netif);#注册默认网络,跟路由相关netif_set_addr(&netif,&ipaddr , &netmask, &gw); #设置地址/* netif is configured */netif_set_up(&netif);ethernet_enable_interrupt();netbios_init();return &netif;tcpip_init拉起来了lwipGET_BOOT_IPADDR获取到写死的IP地址为:#define LWIP_AS_LOCAL_IP_ADDR    "172.18.0.200"#define LWIP_AS_LOCAL_IP_NETMASK"255.255.255.0"#define LWIP_AS_LOCAL_IP_GATEWAY "172.18.0.1"define GET_BOOT_IPADDR ipaddr.addr =ipaddr_addr(LWIP_AS_LOCAL_IP_ADDR)
netif_add(&netif,&ipaddr, &netmask, &gw, NULL, &ethernetif_init,&tcpip_input);
添加网卡,把网卡的ip等信息设置好,初始化执行ethernetif_init,

有包来驱动调用tcpip_input

ethernetif_init函数在

as/com/as.infrastructure/communication/Pci/pci_asnet.c中

PciNet_Init(netif->gw.addr, netif->netmask.addr,netif->hwaddr,&mtu);中

pdev = find_pci_dev_from_id(0xcaac,0x0002); #找到网卡设备,0x0001是can用的,1是网卡
__iobase = pci_get_memio(pdev, 1); #获取寄存器地址Irq_Save(imask);enable_pci_resource(pdev);pci_register_irq(pdev->irq_num,Eth_Isr);enable_pci_interrupt(pdev);writel(__iobase+REG_GW, gw);writel(__iobase+REG_NETMASK, netmask);writel(__iobase+REG_CMD, 0);Irq_Restore(imask);
irq_num的中断号为11,Eth_Isr是中断发生后处理函数,这里靠lwip进程轮询执行,中断没触发。

寄存器偏移地址定义如下:
enum{REG_MACL      = 0x00,REG_MACH      = 0x04,REG_MTU       = 0x08,REG_DATA      =0x0C,REG_LENGTH    = 0x10,REG_NETSTATUS = 0x14,REG_GW        = 0x18,REG_NETMASK   = 0x1C,REG_CMD       = 0x20,REG_ADAPTERID =0x24,};
网卡驱动和ip 端口已经绑定好了,

3.3 TaskLwip激活

系统启动的时候,在EcuM_Init里面调用KSM_INIT() ,然后执行KsmLwipIdle_Init

在as/com/as.infrastructure/system/kernel/Os.c中,LwipIdle初始化之后就会轮询执行功能函数。
staticconst KsmFunction_Type KsmLwipIdle_FunctionList[4] = {     KsmLwipIdle_Init                   ,     KsmLwipIdle_Start                  ,        KsmLwipIdle_Stop                   ,        KsmLwipIdle_Running                ,    };
const KSM_Type KSM_Config[KSM_NUM] =                                                  {     { /* LwipIdle */4,           KsmLwipIdle_FunctionList     },       { /* CANIdle */4,           KsmCANIdle_FunctionList     },   };
voidKsmStart(void){  KsmID_Type i;for(i=0;i<KSM_NUM;i++)  {    KSM_Config.Ksm[KSM_S_START]();  }}

4. PCI网卡驱动程序读取数据

激活lwip任务后,会定时执行KsmLwipIdle_Running函数,定义如下:
KSM(LwipIdle,Running){#ifdef USE_LWIP    Eth_Isr();#endif}
Eth_Isr()在com/as.infrastructure/communication/Pci/pci_asnet.c中实现

之前网卡寄存器的地区获取保存到__iobase =pci_get_memio(pdev, 1);
flag =readl(__iobase+REG_NETSTATUS);if(flag&FLG_RX){struct pbuf *p = low_level_input();    ethhdr = (struct eth_hdr *)p->payload;    witch(htons(ethhdr->type)) {case ETHTYPE_IP:if (ethernet_input(p,netif) != ERR_OK) {            LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));            pbuf_free(p);            p = NULL;}                 low_level_input函数从寄存器里面读取数据len = len2 =readl(__iobase+REG_LENGTH);pkbuf[pos] = readl(__iobase+REG_DATA);
总结下驱动处理数据的主要过程如下:

Eth_Isrstruct pbuf *p = low_level_input();  ethhdr = (struct eth_hdr*)p->payload;switch(htons(ethhdr->type)){case ETHTYPE_IP:      ethernet_input(p,netif)  }

5. Lwip程序

ethernet_input把读取的数据送入入lwip开源软件处理,release/download/lwip/src/netif/etharp.c

这里需要注意lwip的代码在release/download/lwip下,但是做了一个软链接到com里面了,例如:

com/as.infrastructure/system/net/lwip/lwip/src/core/ipv4/ip.c这个文件软链接到:

release/download/lwip/src/core/ipv4/ip.c

ethernet_input传入的是eth报文,然后分类处理
#define ETHTYPE_ARP       0x0806U#define ETHTYPE_IP        0x0800U#define ETHTYPE_VLAN      0x8100U#define ETHTYPE_PPPOEDISC0x8863U  /* PPP Over Ethernet DiscoveryStage */#define ETHTYPE_PPPOE     0x8864U /* PPP Over Ethernet Session Stage */
如果是IP类型:
ip_input(p, netif);#define IP_PROTO_ICMP    1#define IP_PROTO_IGMP    2#define IP_PROTO_UDP     17#define IP_PROTO_UDPLITE 136#define IP_PROTO_TCP     6
Lwip模块具体处理流程为:
ethernet_inputethhdr = (struct eth_hdr *)p->payload;type = ethhdr->type;switch (type) {case PP_HTONS(ETHTYPE_IP):ip_input(p, netif);}ip_input#define IPH_PROTO(hdr) ((hdr)->_proto)iphdr = (struct ip_hdr *)p->payload;switch (IPH_PROTO(iphdr)) {case IP_PROTO_TCP:tcp_input(p, inp);}tcp_input     struct tcp_pcb*pcb;for(pcb = tcp_active_pcbs; pcb != NULL; pcb =pcb->next) {if (pcb->remote_port == tcphdr->src &&      pcb->local_port == tcphdr->dest &&      ip_addr_cmp(&(pcb->remote_ip), &current_iphdr_src) &&      ip_addr_cmp(&(pcb->local_ip), &current_iphdr_dest)) {break;}}tcp_process(pcb);switch (pcb->state) {case SYN_RCVD:    tcp_receive(pcb); //收到报文后存储在lwip协议栈中  }

6. SoAd模块处理

SoAd模块初始化后,TCP的13400端口会处于监听状态,

会循环执行scanSockets中socketAccept()函数来监听,如果lwip协议栈有数据则会返回clientFd
socketAcceptclientFd =SoAd_AcceptImpl(SocketAdminList[sockNr].SocketHandle, &RemoteIpAddress,&Rem     otePort);                                                                                  if( clientFd != (-1)){SocketAdminList[sockNr].SocketState= SOCKET_TCP_READY;}SOCKET_TCP_READY在状态机中会执行socketTcpRead(i);函数socketTcpReadswitch(SocketAdminList[sockNr].SocketConnectionRef->AutosarConnectorType) {case SOAD_AUTOSAR_CONNECTOR_DOIP:  DoIp_HandleTcpRx(sockNr);}
这里服务器端是自己搞的一个接受的socket逻辑,大体上也是跟上面图里的一个步骤。初始化socket,然后等待连接,有连接后读取数据。

到DoIp_HandleTcpRx函数这里就接上了DoIP模块的代码。

7. SoAd规范

    官网文档名字为

《AUTOSAR_SWS_SocketAdaptor.pdf》,SoAd的意思就是SocketAdaptor,就是适配socket的,提供网络的socket编程服务。

SoAd模块的功能描述如下:

AUTOSAR入门-SoAd模块和TcpIp模块w9.jpg

可以看到我们用lwip开源软件直接替代了TcpIp模块,TcpIp就是提供网络协议栈的解析的,还能提供DHCP、ICMP、ARP等报文的服务。

其他SoAd的函数的解释自己可以看下规范。对照as.infrastructure/communication/SoAd/SoAd_LWIP.c代码自己看下。

8. TcpIp规范

规范文档为:《AUTOSAR_SWS_TcpIp.pdf》,目前的as平台上由于Arccore的代码比较老,TcpIp没独立出来一个模块,用Lwip代替了。最新的开源代码里面是有TcpIp模块的,可以参考:https://github.com/openAUTOSAR/classic-platform 进行一个移植。主要功能如下图:

AUTOSAR入门-SoAd模块和TcpIp模块w10.jpg

后记:

    到此网络协议栈相关的AUTOSAR模块都讲完了,如下:网卡驱动-》网络接口-》网络协议栈-》SoAd模块-》DoIP模块-》PduR模块-》Dcm模块。完成了AUTOSAR一小半的主要模块,有兴趣的朋友可以研究下Can相关的协议和模块,基本也是这么个套路,并且can协议比DoIP简单一些。也有can的客户端可以发can报文,qemu模拟can的驱动等。

   最近有很多对自研代码和下一代汽车软件感兴趣的朋友,这里可以加我微信thatway1989,备注进群。然后拉你进本公众号的交流群:OS与AUTOSAR研究-交流群,可以讨论汽车软件最新技术,一起学习。

    Talk is cheap,show me the code,后续会继续更新,纯干货分析,无广告,不打赏,欢迎转载,欢迎评论交流!

往期见话题:AUTOSAR入门


该用户从未签到

 楼主| 发表于 14-3-2025 15:32:04 | 显示全部楼层
AUTOSAR是一个为汽车电子功能软件而开发的标准化架构,关于其入门学习,SoAd模块和TcpIp模块是其中的重要部分。确实,“All is in the code”,代码是技术的核心体现。对于SoAd模块和TcpIp模块的学习,直接阅读相关代码是快速上手的关键。掌握C语言逻辑后,可以触类旁通,无论是操作系统、应用app还是驱动、网络协议等,都可以通过理解代码逻辑来迅速掌握。建议从基础语法开始,逐步深入理解AUTOSAR架构及各模块间的交互,结合代码实践,不断提升编程及问题解决能力。
回复 支持 反对

使用道具 举报



该用户从未签到

发表于 14-3-2025 15:32:04 | 显示全部楼层
关于AUTOSAR入门涉及的SoAd模块和TcpIp模块:

针对AUTOSAR架构,SoAd模块主要涉及软件架构的开发与配置,是软件组件的集成和管理中心。TcpIp模块则关注网络通信,实现车载系统与外部网络的连接。两者在车载系统中扮演着重要角色。

关于“All is in the code”,我完全同意这一观点。代码是技术实现的直接体现,通过研读代码可以迅速了解技术设计思路及实现方式。对于高境界的C程序员而言,掌握不同技术领域中的C代码,的确可以迅速上手并深入理解,因为无论技术如何变化,都是由基础的编程语言逻辑支撑。

因此,建议入门AUTOSAR的新手从学习其模块的基础代码开始,结合实际项目案例进行深入学习与理解,逐渐掌握其核心技术与思想。
回复 支持 反对

使用道具 举报

快速发帖

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|手机版|小黑屋|Archiver|汽车工程师之家 ( 渝ICP备18012993号-1 )

GMT+8, 19-8-2025 10:53 , Processed in 0.304084 second(s), 36 queries .

Powered by Discuz! X3.5

© 2001-2013 Comsenz Inc.