精华内容
下载资源
问答
  • Linux内核socket(一)

    2018-09-18 20:30:26
    要了解内核socket需要先对内核socket的结构进行了解。 其中比较重要的就是struct socket和struct sock两个结构体。 每个socket数据结构都有一个sock数据结构成员,sock是对socket的扩充,两者一一对应,socket->...

    要了解内核socket需要先对内核socket的结构进行了解。

    其中比较重要的就是struct socket和struct sock两个结构体。

    每个socket数据结构都有一个sock数据结构成员,sock是对socket的扩充,两者一一对应,socket->sk指向对应的sock,sock->socket 指向对应的socket;

    socket和sock是同一事物的两个侧面,为什么不把两个数据结构合并成一个呢?这是因为socket是inode结构中的一部分,即把inode结构内部的一个union用作socket结构。由于插口操作的特殊性,这个数据结构中需要有大量的结构成分,如果把这些成分全部放到socket结构中,则inode结构中的这个union就会变得很大,从而inode结构也会变得很大,而对于其他文件系统这个union是不需要这么大的,所以会造成巨大浪费,系统中使用inode结构的数量要远远超过使用socket的数量,故解决的办法就是把插口分成两部分,把与文件系统关系密切的放在socket结构中,把与通信关系密切的放在另一个单独结构sock中; 

    1、struct socket

    struct socket定义在include/linux/net.h中,socket结构体的内容如下:

    struct socket {
    	socket_state		state;
    
    	kmemcheck_bitfield_begin(type);
    	short			type;
    	kmemcheck_bitfield_end(type);
    
    	unsigned long		flags;
    
    	struct socket_wq __rcu	*wq;
    
    	struct file		*file;
    	struct sock		*sk;
    	const struct proto_ops	*ops;
    };

    其中,state是socket的状态,比如CONNECTED;type是类型,比如TCP下使用的流式套接字SOCK_STREAM;flags是标志位,负责一些特殊的设置,比如SOCK_ASYNC_NOSPACE;ops则是采用了和超级块设备操作表一样的逻辑,专门设置了一个数据结构来记录其允许的操作。sk是非常重要的,也是非常大的,负责记录协议相关内容。这样的设置使得socket具有很好的协议无关性,可以通用。file是与socket相关的指针列表,wq是等待队列。

    还有两个结构体sk_buff和tcp_sock,其中sk_buff与每一个数据包相关,负责描述每一个数据包的信息,而tcp_sock则是tcp相关的结构体。

    在struct socket结构体中,也定义了一些socket的操作函数:const struct proto_ops *ops;

    struct proto_ops同样定义在include/linux/net.h中,其主要内容如下:

    2、struct sock

    struct sock定义在include/net/sock.h中,sock结构的内容如下所示:

    struct sock {
    	/*
    	 * Now struct inet_timewait_sock also uses sock_common, so please just
    	 * don't add nothing before this first member (__sk_common) --acme
    	 */
    	struct sock_common	__sk_common;
    #define sk_node			__sk_common.skc_node
    #define sk_nulls_node		__sk_common.skc_nulls_node
    #define sk_refcnt		__sk_common.skc_refcnt
    #define sk_tx_queue_mapping	__sk_common.skc_tx_queue_mapping
    
    #define sk_dontcopy_begin	__sk_common.skc_dontcopy_begin
    #define sk_dontcopy_end		__sk_common.skc_dontcopy_end
    #define sk_hash			__sk_common.skc_hash
    #define sk_portpair		__sk_common.skc_portpair
    #define sk_num			__sk_common.skc_num
    #define sk_dport		__sk_common.skc_dport
    #define sk_addrpair		__sk_common.skc_addrpair
    #define sk_daddr		__sk_common.skc_daddr
    #define sk_rcv_saddr		__sk_common.skc_rcv_saddr
    #define sk_family		__sk_common.skc_family
    #define sk_state		__sk_common.skc_state
    #define sk_reuse		__sk_common.skc_reuse
    #define sk_reuseport		__sk_common.skc_reuseport
    #define sk_ipv6only		__sk_common.skc_ipv6only
    #define sk_net_refcnt		__sk_common.skc_net_refcnt
    #define sk_bound_dev_if		__sk_common.skc_bound_dev_if
    #define sk_bind_node		__sk_common.skc_bind_node
    #define sk_prot			__sk_common.skc_prot
    #define sk_net			__sk_common.skc_net
    #define sk_v6_daddr		__sk_common.skc_v6_daddr
    #define sk_v6_rcv_saddr	__sk_common.skc_v6_rcv_saddr
    #define sk_cookie		__sk_common.skc_cookie
    #define sk_incoming_cpu		__sk_common.skc_incoming_cpu
    #define sk_flags		__sk_common.skc_flags
    #define sk_rxhash		__sk_common.skc_rxhash
    
    	socket_lock_t		sk_lock;
    	struct sk_buff_head	sk_receive_queue;
    	/*
    	 * The backlog queue is special, it is always used with
    	 * the per-socket spinlock held and requires low latency
    	 * access. Therefore we special case it's implementation.
    	 * Note : rmem_alloc is in this structure to fill a hole
    	 * on 64bit arches, not because its logically part of
    	 * backlog.
    	 */
    	struct {
    		atomic_t	rmem_alloc;
    		int		len;
    		struct sk_buff	*head;
    		struct sk_buff	*tail;
    	} sk_backlog;
    #define sk_rmem_alloc sk_backlog.rmem_alloc
    	int			sk_forward_alloc;
    
    	__u32			sk_txhash;
    #ifdef CONFIG_NET_RX_BUSY_POLL
    	unsigned int		sk_napi_id;
    	unsigned int		sk_ll_usec;
    #endif
    	atomic_t		sk_drops;
    	int			sk_rcvbuf;
    
    	struct sk_filter __rcu	*sk_filter;
    	union {
    		struct socket_wq __rcu	*sk_wq;
    		struct socket_wq	*sk_wq_raw;
    	};
    #ifdef CONFIG_XFRM
    	struct xfrm_policy __rcu *sk_policy[2];
    #endif
    	struct dst_entry	*sk_rx_dst;
    	struct dst_entry __rcu	*sk_dst_cache;
    	/* Note: 32bit hole on 64bit arches */
    	atomic_t		sk_wmem_alloc;
    	atomic_t		sk_omem_alloc;
    	int			sk_sndbuf;
    	struct sk_buff_head	sk_write_queue;
    	kmemcheck_bitfield_begin(flags);
    	unsigned int		sk_shutdown  : 2,
    				sk_no_check_tx : 1,
    				sk_no_check_rx : 1,
    				sk_userlocks : 4,
    				sk_protocol  : 8,
    				sk_type      : 16;
    #define SK_PROTOCOL_MAX U8_MAX
    	kmemcheck_bitfield_end(flags);
    	int			sk_wmem_queued;
    	gfp_t			sk_allocation;
    	u32			sk_pacing_rate; /* bytes per second */
    	u32			sk_max_pacing_rate;
    	netdev_features_t	sk_route_caps;
    	netdev_features_t	sk_route_nocaps;
    	int			sk_gso_type;
    	unsigned int		sk_gso_max_size;
    	u16			sk_gso_max_segs;
    	int			sk_rcvlowat;
    	unsigned long	        sk_lingertime;
    	struct sk_buff_head	sk_error_queue;
    	struct proto		*sk_prot_creator;
    	rwlock_t		sk_callback_lock;
    	int			sk_err,
    				sk_err_soft;
    	u32			sk_ack_backlog;
    	u32			sk_max_ack_backlog;
    	__u32			sk_priority;
    #if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
    	__u32			sk_cgrp_prioidx;
    #endif
    	struct pid		*sk_peer_pid;
    	const struct cred	*sk_peer_cred;
    	long			sk_rcvtimeo;
    	long			sk_sndtimeo;
    	struct timer_list	sk_timer;
    	ktime_t			sk_stamp;
    	u16			sk_tsflags;
    	u32			sk_tskey;
    	struct socket		*sk_socket;
    	void			*sk_user_data;
    	struct page_frag	sk_frag;
    	struct sk_buff		*sk_send_head;
    	__s32			sk_peek_off;
    	int			sk_write_pending;
    #ifdef CONFIG_SECURITY
    	void			*sk_security;
    #endif
    	__u32			sk_mark;
    #ifdef CONFIG_CGROUP_NET_CLASSID
    	u32			sk_classid;
    #endif
    	struct cg_proto		*sk_cgrp;
    	void			(*sk_state_change)(struct sock *sk);
    	void			(*sk_data_ready)(struct sock *sk);
    	void			(*sk_write_space)(struct sock *sk);
    	void			(*sk_error_report)(struct sock *sk);
    	int			(*sk_backlog_rcv)(struct sock *sk,
    						  struct sk_buff *skb);
    	void                    (*sk_destruct)(struct sock *sk);
    };

     __sk_common是套接口在网络层的最小表示,struct sock_common同样定义在include/net/sock.h中;

        sk_shutdown是一组标志位,SEND_SHUTDOWN and/or RCV_SHUTDOWN。

        sk_userlocks, SO_SNDBUF and SO_RCVBUF。

        sk_rcvbuf表示接收缓冲区的字节长度。

        sk_rmem_alloc表示接收队列已提交的字节数。

        sk_receive_queue表示接收的数据包的队列。

        sk_wmem_alloc表示发送队列已提交的字节数。

        sk_write_queue表示发送数据包的队列。

        sk_sndbuf表示发送缓冲区的字节长度。

        sk_flags,SO_LINGER (l_onoff),SO_BROADCAST,SO_KEEPALIVE,SO_OOBINLINE。

        sk_prot是指定的域内部的协议处理函数集,它是套接口层跟传输层之间的一个接口,提供诸如bind, accept, close等操作。

        sk_ack_backlog表示当前的侦听队列。

        sk_max_ack_backlog表示最大的侦听队列。

        sk_type表示套接字的类型,如SOCK_STREAM。

        sk_protocol表示在当前域中套接字所属的协议。

       几个函数指针均属回调函数,分别在套接口状态变化,有数据到达需要处理,有发送空间可用,有错误等时候被回调。最后一个函数sk_destruct在套接口释放时被回调。

     

    展开全文
  • Linux内核Socket CAN中文文档 网址:http://blog.csdn.net/yuanlulu/article/details/7220060 自己在年假中空闲之余翻译的内核中Socket CAN的文档,原文地址在: ...

    Linux内核Socket CAN中文文档


    网址:http://blog.csdn.net/yuanlulu/article/details/7220060



    自己在年假中空闲之余翻译的内核中Socket CAN的文档,原文地址在:

    http://lxr.linux.no/linux+v2.6.34/Documentation/networking/can.txt

    但是这篇文档没有涉及广播管理协议套接字 (SOCK_DGRAM) 的内容。

    另外一篇比较好的Socket CAN的英文文档是(详细介绍了广播管理协议套接字):

    Low Level CAN Framework Application Programmers Interface

    http://www.brownhat.org/docs/socketcan/llcf-api.html#SECTION00051000000000000000

    自己暂时没时间翻译这篇文章了,有空再说吧。

     

    自己英文水平有限,希望高人指正。

    ================================================================================

     

    这篇文章主要针对can协议簇(aka socket can)

    这篇文章包含以下内容:
    ===============
    1 概述--什么是Socket CAN?

    2 动机--为什么使用socket API接口?

    3 Socket CAN详解
         3.1 接收队列
         3.2 发送帧的本地回环
         3.3 网络安全相关
         3.4 网络故障监测

    4 如何使用Socket CAN
         4.1 使用can_filter的原始套接字 (RAW socket)
              4.1.1 原始套接字选项 CAN_RAW_FILTER
              4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER
              4.1.3 原始套接字选项 CAN_RAW_LOOPBACK
              4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS
         4.2 广播管理协议套接字 (SOCK_DGRAM)
         4.3 面向连接的传输协议 (SOCK_SEQPACKET)
         4.4 无连接的传输协议  (SOCK_DGRAM)

    5 Socket CAN核心模块
         5.1 can.ko模块的参数
         5.2 procfs接口
         5.3 写一个自己的CAN协议模块

    6 CAN网络驱动
         6.1 常见设置
         6.2 发送帧的本地回环
         6.3 CAN控制器的硬件过滤
         6.4 虚拟的CAN驱动 (vcan)
         6.5 CAN网络设备驱动接口
              6.5.1 Netlink接口--设置/获取设备属性
              6.5.2 设置CAN的比特_时序
              6.5.3 启动和停止CAN网络设备
         6.6 支持Socket CAN的硬件

    7 学习Socket CAN的相关资源

    8 贡献者名单

    ==============现在开始===================
    1. 概述--什么是Socket CAN?
    ==================

    socketcan子系统是在Linux下CAN协议(Controller Area Network)实现的一种实现方法。 CAN是一种在世界范围内广泛用于自动控制、嵌入式设备和汽车领域的网络技术。Linux下最早使用CAN的方法是基于字符设备来实现的,与之不同的是Socket CAN使用伯克利的socket接口和linux网络协议栈,这种方法使得can设备驱动可以通过网络接口来调用。Socket CAN的接口被设计的尽量接近TCP/IP的协议,让那些熟悉网络编程的程序员能够比较容易的学习和使用。

    2. 动机--为什么使用socket API接口?
    =======================

    在Socket CAN之前Linux中已经有了一些CAN的实现方法,那为什么还要启动Socket CAN这个项目呢?大多数已经存在的实现方法仅仅作为某个具体硬件的设备驱动,它们往往基于字符设备并且提供的功能很少。那些方案通常是由一个针对具体硬件的设备驱动提供的字符设备接口来实现原始can帧的发送和接收,并且直接和控制器硬件打交道。帧队列和ISO-TP这样的高层协议必须在用户空间来实现。就像串口设备接口一样,大多数基于字符设备的实现在同一时刻仅仅支持一个进程的访问。如果更换了CAN控制器,那么同时也要更换另一个设备驱动,并且需要大多数应用程序重新调整以适应新驱动的API。

    Socket CAN被设计用来克服以上种种不足。这种新的协议族实现了用户空间的socket接口,它构建于Linux网络层之上,因此可以直接使用已有的队列功能。CAN控制器的设备驱动将自己作为一个网络设备注册进Linux的网络层,CAN控制器收到的CAN帧可以传输给高层的网络协议和CAN协议族,反之,发送的帧也会通过高层给CAN控制器。传输协议模块可以使用协议族提供的接口注册自己,所以可以动态的加载和卸载多个传输协议。事实上,CAN核心模块不提供任何协议,也不能在没有加载其它协议的情况下单独使用。同一时间可以在相同或者不同的协议上打开多个套接字,可以在相同或者不同的CAN ID上同时监听和发送(listen/send)。几个同时监听具有相同ID帧的套接字可以在匹配的帧到来后接收到相同的内容。如果一个应用程序希望使用一个特殊的协议(比如ISO-TP)进行通信,只要在打开套接字的时候选择那个协议就可以了,接下来就可以读取和写入应用数据流了,根本无需关心CAN-ID和帧的结构等信息。


    使用字符设备也可以让用户空间获得类似的便利,但是这中解决方案在技术上不够优雅,原因如下:

    *复杂的操作。使用Socket CAN,只需要向socket(2)函数传递协议参数并使用bind(2)选择CAN接口和CAN ID,基于字符设备实现这样的功能却要使用ioctl(2)来完成所有的操作。

    *无法代码复用。字符设备无法使用Linux的网络队列代码,所以必须为CAN 网络重写这部分功能。

    *缺乏抽象性。在大多数已经实现的字符设备方案中,硬件相关的CAN控制器设备驱动直接提供应用程序需要的字符设备。在Unix系统中,无论对于字符设备还是块设备,这都是不常见的。比如,你不会为某个串口、电脑上的某个声卡、访问磁带和硬盘的SCSI/IDE控制器直接创建一个字符设备。相反,你会将向应用程序提供统一字符设备/块设备接口的功能交给一个抽象层来做,你自己仅仅提供硬件相关的设备驱动接口。这种抽象是通过子系统来完成的,比如tty层子系统、声卡子系统和SCSI/IDE子系统。

    实现CAN设备驱动最简单的办法就是直接提供一个字符设备而不使用(或不完全使用)抽象层,大多数已经存在的驱动也是这么做的。但是正确的方法却要增加抽象层以支持各种功能,如注册一个特定的CAN-ID,支持多次打开的操作和这些操作之间的CAN帧复用,支持CAN帧复杂的队列功能,还要提供注册设备驱动的API。然而使用Linux内核提供的网络框架将会大大简化,这就是Socket CAN要做的。

    在Linux中实现CAN功能最自然和合适的方式就是使用内核的网络框架。

    3. Socket CAN详解
    ============

    就像第二章所讲的那样,使用Socket CAN的主要目的就是为用户空间的应用程序提供基于Linux网络层的套接字接口。与广为人知的TCP/IP协议以及以太网不同,CAN总线没有类似以太网的MAC层地址,只能用于广播。CAN ID仅仅用来进行总线的仲裁。因此CAN ID在总线上必须是唯一的。当设计一个CAN-ECU(Electronic Control Unit 电子控制单元)网络的时候,CAN-ID可以映射到具体的ECU。因此CAN-ID可以当作发送源的地址来使用。

    3.1 接收队列
    ---------------

    允许多个应用程序同时访问网络导致了新的问题出现,那就是不同的应用程序可能会在同一个CAN网络接口上对具有相同CAN-ID的帧感兴趣。Socket CAN的核心部分实现了Socket CAN的协议族,通过高效的接收队列解决了这个问题。比如一个用户空间的程序打开了一个原始CAN套接字,原始协议模块将向CAN套接字的核心模块申请用户空间需要的一系列CAN-ID。Socket CAN的核心向CAN协议模块提供预约和解约CAN-ID的接口--can_rx_(un)register(),无论这个CAN-ID是针对一个具体的CAN接口还是所有已知的CAN接口(参考第5章)。

    为了优化CPU的运行效率,每个设备都对应一个接收队列,这样比较容易实现各种报文过滤规则。

    3.2 发送帧的本地回环
    -----------------

    在其它种类的网络中,在相同或者不同网络节点上的应用程序都可以相互交换数据。

             ___   ___   ___                   _______   ___
            | _ | | _ | | _ |                 | _   _ | | _ |
            ||A|| ||B|| ||C||                 ||A| |B|| ||C||
            |___| |___| |___|                 |_______| |___|
              |     |     |                       |       |
            -----------------(1)- CAN bus -(2)---------------



    请看上图的两个例子。为了保证应用程序A在两个例子中能够接收到同样的例子(例2中A和B在同一个CAN设备上),发送出去的CAN帧需要能够在本地回环。

    Linux下的网络设备仅仅处理物理媒介上帧的发送和接受。总线仲裁机制下,高优先级的帧会将低优先级的帧延后。为了正确反映实际的通信活动,回环必须在正确传输成功之后进行。如果CAN网络的硬件不支持回环功能,一种低效的方案是使用Socket CAN核心部分来实现软件回环。具体的情况请参考6.2小节。

    CAN网络的回环功能是默认开启的。由于RT-SocketCAN 的特殊需求,每个套接字的回环功能可以被独立关闭。CAN原始套接字的控制选项请参考4.1小节。

    *当你在同一个节点上运行CAN分析命令“candump”或者“cansniffer”的时候就会发现回环功能真的很有用。

    3.3 网络安全相关
    --------------------

    CAN网络是一种现场总线,仅仅支持没有路由和安全控制的广播传输。大部分应用程序都需要要直接处理原始CAN帧,所以和其它类型的网络一样,CAN网络对所有的用户(而不仅仅是root用户)访问没有任何限制。由于当前CAN_RAW和CAN_BCM的实现仅仅支持对CAN接口的读写,所以允许所有的用户访问CAN并不影响其它类型网络的安全性。为了使能非root用户对CAN_RAW和CAN_BCM协议套接字的访问,必须在编译内核的时候选上Kconfig的CAN_RAW_USER/CAN_BCM_USER选项。

    3.4 网络故障监测
    --------------------

    使用CAN总线的时候,可能会遇到物理和mac(media access control)层的问题。为了方便用户分析物理收发器的硬件错误、总线仲裁错误和不同的ECU( Electrical Conversion Unit,电气转换装置)引起的错误,对于底层(物理和mac层)的监测和记录是至关重要的。拥有精确时间戳的错误监测对于诊断错误是非常重要的。基于以上原因,CAN接口的驱动可以可以选择性的产生所谓的错误帧,这些帧以和其它的CAN帧一样的方式传递给用户程序。当一个物理层或者MAC层的错误被(CAN控制器)检测到之后,驱动创建一个相应的错误帧。错误帧可以被应用程序通过CAN的过滤机制请求得到。过滤机制允许选择需要的错误帧的类型。默认情况下,接收错误帧的功能是禁止的。
    CAN错误帧的详细格式定义在linux头文件中:include/linux/can/error.h。

    4. 如何使用Socket CAN
    ===============

    就像TCP/IP协议一样,在使用CAN网络之前你首先需要打开一个套接字。CAN的套接字使用到了一个新的协议族,所以在调用socket(2)这个系统函数的时候需要将PF_CAN作为第一个参数。当前有两个CAN的协议可以选择,一个是原始套接字协议( raw socket protocol),另一个是广播管理协议BCM(broadcast manager)。你可以这样来打开一个套接字:

         s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    或者
         s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);

    在成功创建一个套接字之后,你通常需要使用bind(2)函数将套接字绑定在某个CAN接口上(这和TCP/IP使用不同的IP地址不同,参见第3章)。在绑定 (CAN_RAW)或连接(CAN_BCM)套接字之后,你可以在套接字上使用read(2)/write(2),也可以使用send(2)/sendto(2)/sendmsg(2)和对应的recv*操作。当然也会有CAN特有的套接字选项,下面将会说明。

    基本的CAN帧结构体和套接字地址结构体定义在include/linux/can.h:

    /*
    * 扩展格式识别符由 29 位组成。其格式包含两个部分:11 位基本 ID、18 位扩展 ID。
    * Controller Area Network Identifier structure
    *
    * bit 0-28     : CAN识别符 (11/29 bit)
    * bit 29     : 错误帧标志 (0 = data frame, 1 = error frame)
    * bit 30     : 远程发送请求标志 (1 = rtr frame)
    * bit 31     :帧格式标志 (0 = standard 11 bit, 1 = extended 29 bit)
    */
    typedef __u32 canid_t;

    struct can_frame { 
         canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
          __u8    can_dlc; /* 数据长度: 0 .. 8 */
          __u8    data[8] __attribute__((aligned(8))); 
     };

    结构体的有效数据在data[]数组中,它的字节对齐是64bit的,所以用户可以比较方便的在data[]中传输自己定义的结构体和共用体。CAN总线中没有默认的字节序。在CAN_RAW套接字上调用read(2),返回给用户空间的数据是一个struct can_frame结构体。

    就像PF_PACKET套接字一样,sockaddr_can结构体也有接口的索引,这个索引绑定了特定接口:

        struct sockaddr_can { 
              sa_family_t can_family;
              int         can_ifindex;
              union { 
                        /* transport protocol class address info (e.g. ISOTP) */
                        struct { canid_t rx_id, tx_id; } tp;
                        /* reserved for future CAN protocols address information */ 
              } can_addr; 
        };

    指定接口索引需要调用ioctl()(比如对于没有错误检查CAN_RAW套接字):

        int s;
        struct sockaddr_can addr;
        struct ifreq ifr;

        s = socket(PF_CAN, SOCK_RAW, CAN_RAW);

        strcpy(ifr.ifr_name, "can0" );
        ioctl(s, SIOCGIFINDEX, &ifr);

        addr.can_family = AF_CAN;
        addr.can_ifindex = ifr.ifr_ifindex;

        bind(s, (struct sockaddr *)&addr, sizeof(addr));

        (..)

    为了将套接字和所有的CAN接口绑定,接口索引必须是0。这样套接字便可以从所有使能的CAN接口接收CAN帧。recvfrom(2)可以指定从哪个接口接收。在一个已经和所有CAN接口绑定的套接字上,sendto(2)可以指定从哪个接口发送。

    从一个CAN_RAW套接字上读取CAN帧也就是读取struct can_frame结构体:

        struct can_frame frame;

        nbytes = read(s, &frame, sizeof(struct can_frame));

        if (nbytes < 0) {
                perror("can raw socket read");
                return 1;
        }

        /* paranoid check ... */
        if (nbytes < sizeof(struct can_frame)) {
                fprintf(stderr, "read: incomplete CAN frame\n");
                return 1;
        }

        /* do something with the received CAN frame */

    写CAN帧也是类似的,需要用到write (2)函数:

        nbytes = write(s, &frame, sizeof(struct can_frame)); 

    如果套接字跟所有的CAN接口都绑定了(addr.can_index = 0),推荐使用recvfrom(2)获取数据源接口的信息:

        struct sockaddr_can addr;
        struct ifreq ifr;
        socklen_t len = sizeof(addr);
        struct can_frame frame;

        nbytes = recvfrom(s, &frame, sizeof(struct can_frame),
                          0, (struct sockaddr*)&addr, &len);

        /* get interface name of the received CAN frame */
        ifr.ifr_ifindex = addr.can_ifindex;
        ioctl(s, SIOCGIFNAME, &ifr);
        printf("Received a CAN frame from interface %s", ifr.ifr_name);

    对于绑定了所有接口的套接字,向某个端口发送数据必须指定接口的详细信息:

        strcpy(ifr.ifr_name, "can0");
        ioctl(s, SIOCGIFINDEX, &ifr);
        addr.can_ifindex = ifr.ifr_ifindex;
        addr.can_family  = AF_CAN;

        nbytes = sendto(s, &frame, sizeof(struct can_frame),
                        0, (struct sockaddr*)&addr, sizeof(addr));

    4.1 使用can_filter的原始套接字 (RAW socket)
    ----------------------------------------------------

    CAN_RAW套接字的用法和CAN字符设备的用法是类似的。为了使用CAN套接字的新特性,在绑定原始套接字的时候将会默认开启以下特性:

    - filter将会接收所有的数据
    - 套接字仅仅接收有效的数据帧(=> no error frames)
    - 发送帧的回环功能被开启(参见 3.2节)
    - (回环模式下)套接字不接收它自己发送的帧

    这些特性的设置可以在绑定之前和之后修改。为了使用CAN_RAW套接字相关的选项,必须包含<linux/can/raw.h>。

    • 4.1.1 原始套接字选项 CAN_RAW_FILTER
    CAN_RAW套接字的接收可以使用CAN_RAW_FILTER套接字选项指定的多个过滤规则(过滤器)来过滤。

    过滤规则(过滤器)的定义在 include/linux/can.h中:

        struct can_filter {
                canid_t can_id;
                canid_t can_mask;
        };

    过滤规则的匹配:

        <received_can_id> & mask == can_id & mask

    /*
    #define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
    #define CAN_ERR_FLAG   0x20000000U /* error frame */
    */
    这和大家所熟知的CAN控制器硬件过滤非常相似。可以使用 CAN_INV_FILTER这个宏将can_filter结构体的成员can_id中的比特位反转。和CAN控制器的硬件过滤形成鲜明对比的是,用户可以为每一个打开的套接字设置多个独立的过滤规则(过滤器):

    /*
     /* valid bits in CAN ID for frame formats */
    #define CAN_SFF_MASK 0x000007FFU /* 标准帧格式 (SFF) */
    #define CAN_EFF_MASK 0x1FFFFFFFU /* 扩展帧格式 (EFF) */
    #define CAN_ERR_MASK 0x1FFFFFFFU /* 忽略EFF, RTR, ERR标志 */
     */

        struct can_filter rfilter[2];

        rfilter[0].can_id   = 0x123;
        rfilter[0].can_mask = CAN_SFF_MASK;
        rfilter[1].can_id   = 0x200;
        rfilter[1].can_mask = 0x700;

        setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

    为了在指定的CAN_RAW套接字上禁用接收过滤规则,可以这样:

      setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);

    在一些极端情况下不需要读取数据,可以把过滤规则清零(所有成员设为0),这样原始套接字就会忽略接收到的CAN帧。在这种仅仅发送数据(不读取)的应用中可以在内核中省略接收队列,以此减少CPU的负载(虽然只能减少一点点)。

    • 4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER
    正如3.4节所说,CAN接口驱动可以选择性的产生错误帧,错误帧和正常帧以相同的方式传给应用程序。可能产生的错误被分为不同的种类,使用适当的错误掩码可以过滤它们。为了注册所有可能的错误情况,CAN_ERR_MASK(0x1FFFFFFFU)这个宏可以用来作为错误掩码。这个错误掩码定义在linux/can/error.h。

        can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );

        setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER,
                   &err_mask, sizeof(err_mask));

    • 4.1.3 原始套接字选项 CAN_RAW_LOOPBACK
    为了满足众多应用程序的需要,本地回环功能默认是开启的(详细情况参考3.2节)。但是在一些嵌入式应用场景中(比如只有一个用户在使用CAN总线),回环功能可以被关闭(各个套接字之间是独立的):

        int loopback = 0; /* 0 = disabled, 1 = enabled (default) */

        setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

    • 4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS
    在本地回环功能开启的情况下,所有的发送帧都会被回环到在相应CAN接口上注册了同样CAN-ID(和发送帧的相同)的套接字上。发送CAN帧的套接字被假设不想接收自己发送的CAN帧,因此在发送套接字上的回环功能默认是关闭的。可以在需要的时候改变这一默认行为:

        int recv_own_msgs = 1; /* 0 = disabled (default), 1 = enabled */

        setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
                   &recv_own_msgs, sizeof(recv_own_msgs));

    (yll:下面三小节没有内容)
    4.2 广播管理协议套接字 (SOCK_DGRAM)
    -----------------------------------------------

    4.3 面向连接的传输协议 (SOCK_SEQPACKET)
    ----------------------------------------------------

    4.4 无连接的传输协议  (SOCK_DGRAM)
    ---------------------------------------------

    5. Socket CAN核心模块
    ==============

    CAN套接字的核心模块实现了PF_CAN协议族。CAN协议模块在核心模块运行后加载。CAN核心模块为协议模块提供了申请需要的ID的接口(参考3.1小节)。

    5.1 can.ko模块的参数
    -------------------------

    - stats_timer: 为了计算CAN套接字的统计信息(比如最近一秒的帧数和每秒最大的帧数),can.ko调用一个定时间隔为1秒的定时器,默认情况下这个定时器是开启的。这个定时器也可以在模块参数中传入stattimer=0来禁止。

    - debug :  (removed since SocketCAN SVN r546)

    5.2 procfs接口
    -----------------

    就像3.1节描述的那样,CAN套接字核心借助于一些带有过滤规则的队列向CAN协议模块传递接收到的CAN帧。可以在procfs中查看这些接收队列的的过滤规则和匹配规则的次数。所有的条目都包了设备名和协议模块标识:

        foo@bar:~$ cat /proc/net/can/rcvlist_all
        
        receive list 'rx_all': (vcan3: no entry)
          (vcan2: no entry)
          (vcan1: no entry)
          device   can_id   can_mask  function  userdata   matches  ident
          vcan0     000    00000000  f88e6370  f6c6f400         0       raw
         (any: no entry)
         (yll补充:function是一个函数指针,userdata是一个void *指针,所以这两个值打印出来没有太大意义)

    在这个例子中,这个应用程序接收所有vcan0上传输的数据.

         rcvlist_all - 没有过滤规则的队列
         rcvlist_eff - 扩展帧(EFF)的队列
         rcvlist_err - 错误帧队列
         rcvlist_fil - 通过过滤规则的队列
         rcvlist_inv - 未通过过滤规则的队列
         rcvlist_sff - 标准帧的队列

    在/proc/net/can中还有另外一些文件:

         stats - CAN套接字核心的统计信息(接收/发送的帧数,匹配率等)
         reset_stats - 复位统计信息
         version - 打印CAN套接字核心的版本和ABI的版本

    5.3 写一个自己的CAN协议模块
    -----------------------------------

    要在PF_CAN中增加一个新的协议,必须在include/linux/can.h中为新的协议增加相应的定义。包含include/linux/can.h这个文件便可以使用增加的原型和定义。内核除了提供了注册CAN协议和CAN设备的通知列表的功能,也提供了在一个特定CAN接口上注册感兴趣的CAN帧或者发送CAN帧的功能。

        can_rx_register   - 在一个特定接口上注册希望接收到的CAN帧的信息  (yll:这个函数的定义在内核的net/can/af_can.c中)
        can_rx_unregister - 注销上面的申请 
        can_send          - 发送CAN帧(可以选择是否开启本地回环) 

    详细的信息请参考内核中的源码net/can/af_can.c、net/can/raw.c、net/can/bcm.c。

    6. CAN网络驱动
    ==========
    编写一个CAN网络设备驱动要比写一个CAN字符设备驱动要容易的多。和编写其它网络设备驱动类似,你只要处理以下事宜:

    - TX :将套接字缓冲区的CAN帧发送到CAN控制器
    - RX :从CAN控制器的CAN帧读取到套接字缓冲区

    Documentation/networking/netdevices.txt中是网络设备驱动的例子。下面将会介绍CAN网络设备驱动的一些独有特性。

    6.1 常见设置
    ---------------

        dev->type  = ARPHRD_CAN; /* the netdevice hardware type */
        dev->flags = IFF_NOARP;  /* CAN has no arp */
        dev->mtu   = sizeof(struct can_frame);

    结构体can_frame是PF_CAN协议族套接字缓冲区的数组载体。

    6.2 发送帧的本地回环
    -------------------------

    如3.2小节所述,CAN网络设备驱动应该支持类似TTY设备回显功能的本地回环功能。如果驱动支持这个功能,则要设备IFF_ECHO标志来防止PF_CAN核心回显发送帧(又称回环)。

        dev->flags = (IFF_NOARP | IFF_ECHO);

    6.3 CAN控制器的硬件过滤
    ------------------------------

    为了减小一些嵌入式系统的中断负载,一些CAN控制器支持多个CAN-ID或者多个CAN-ID区间的过滤功能。硬件过滤功能在不同的控制器之间差异很大,并且不能同时满足多个用户的不同过滤需求。在单用户应用中使用控制器的硬件过滤或许还有意义,但是在一个多用户系统中驱动层的过滤将会影响所有用户。PF_CAN核心内置的过滤规则集合允许对每个套接字独立的设置多个过滤规则。因此使用硬件过滤属于嵌入式系统中“手动调整”的范畴。从2002年开始笔者一直使用拥有四路SJA1000 CAN控制器的MPC603e @133MHz,总线负载虽然很高,但是到目前为止还没有什么问题。

    6.4 虚拟的CAN驱动 (vcan)
    ------------------------------

    和网络回环设备一样,vcan提供一个虚拟的本地CAN接口。CAN中一个有效的地址包括:

    - 一个有效的CAN标识符(CAN ID)
    - 这个CAN标识符将要发往的总线(比如 can0).

    所以在一般的应用场景中往往需要多个vcan接口。

    vcan接口允许在没有控制器硬件的情况下进行发送和接收。vcan网络设备的命名一般采用‘vcanX’,比如can1 vcan2等。当编译为单独的模块的时候,vcan驱动的模块名为vcan.ko。

    vcan驱动从linux2.6.24开始支持netlink接口,使得创建vcan网络设备变的可能。可以使用ip(8)命令工具管理vcan网络设备的创建和移除:

     - 创建一个vcan网络接口:
            $ ip link add type vcan

    - 使用给定的名字 'vcan42'创建 一个vcan网络接口:
           $ ip link add dev vcan42 type vcan

    - 移除vcan网络接口'vcan42':
           $ ip link del vcan42  

    6.5 CAN网络设备驱动接口
    ------------------------------
     
    CAN网络设备驱动提供了进行安装、配置和监控CAN网络设备的接口。可以使用IPROUTE2工具集中的“ip”命令通过netlink接口配置CAN设备,比如设置波特率。本章剩余的部分将会简介如何使用这一工具。另外这些接口使用一些通用的数据结构并且提供了一系列常用的功能,这些功能都是CAN网络设备驱动需要用到的。请参考SJA1000或者MSCAN的驱动去了解如何使用它们。模块的名字是can-dev.ko。
    • 6.5.1 Netlink接口--设置/获取设备属性
    CAN设备必须使用netlink来配置。在"include/linux/can/netlink.h"有对netlink消息类型的定义和简短描述。IPROUTE2工具集中的“ip”命令可以使用CAN的netlink支持,下面是使用示例:

    - 设置CAN设备属性:

        $ ip link set can0 type can help
        Usage: ip link set DEVICE type can [ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |
        [ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1 phase-seg2 PHASE-SEG2 [ sjw SJW ] ]
        [ loopback { on | off } ]
        [ listen-only { on | off } ]
        [ triple-sampling { on | off } ]
        [ restart-ms TIME-MS ]
        [ restart ]
        Where: BITRATE       := { 1..1000000 } SAMPLE-POINT  := { 0.000..0.999 }
              TQ            := { NUMBER }
              PROP-SEG      := { 1..8 }
              PHASE-SEG1    := { 1..8 }
              PHASE-SEG2    := { 1..8 }
              SJW           := { 1..4 }
              RESTART-MS    := { 0 | NUMBER }

    - 显示CAN设备的详情和统计信息:

        $ ip -details -statistics link show can0
     2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP qlen 10 
         link/can
          can <TRIPLE-SAMPLING> state ERROR-ACTIVE restart-ms 100
          bitrate 125000 sample_point 0.875
          tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
          sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
          clock 8000000
          re-started bus-errors arbit-lost error-warn error-pass bus-off 
          41         17457      0          41         42         41
          RX: bytes  packets  errors  dropped overrun mcast 
          140859     17608    17457   0       0       0
          TX: bytes  packets  errors  dropped carrier collsns 
          861        112      0       41      0       0

    下面是上面一些名词的解释:

        "<TRIPLE-SAMPLING>"
    表示选中的CAN控制器的模式:LOOPBACK, LISTEN-ONLY, or TRIPLE-SAMPLING。

        "state ERROR-ACTIVE"
    CAN控制器的当前状态:"ERROR-ACTIVE", "ERROR-WARNING", "ERROR-PASSIVE", "BUS-OFF" or "STOPPED"

        "restart-ms 100"
    自动重启的延时时间。如果设为非零值, 在总线关闭的情况下,在设定的数量毫秒后CAN控制器被自动触发。这个功能默认是关闭的。

        "bitrate 125000 sample_point 0.875"
    使用bits/sec作为单位显示位时间并显示0.000~0.999的采样点位置。如果内核中使能了统计位时间的功能(CONFIG_CAN_CALC_BITTIMING=y),位时间可以使用"bitrate"参数来设置。可选的"sample-point"也是可以配置的。默认使用0.000这个推荐值。

        "tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1"
    以ns为单位显示时间份额(tq-time quanta)、传播段(prop-seg : propagation segment)、相位缓冲段1和2(phase-seg:phase buffer),以tq为单位显示同步跳转宽度(sjw:synchronisation jump width)。这些变量允许定义与硬件无关的位时序,这也是Bosch CAN 2.0 spec所推荐的(参考第八章http://www.semiconductors.bosch.de/pdf/can2spec.pdf)。

        "sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 clock 8000000"
    显示CAN控制器的比特时序常量,这里的例子以sja1000为例。时间段(tseg -time segment)1和2的最小和最大值,以tq为单位的同步跳转宽度,比特速率的预分频器(brp--pre-scaler)和CAN系统时钟(以HZ为单位)。这些常量可以被用户空间的比特时序统计算法所使用。

        "re-started bus-errors arbit-lost error-warn error-pass bus-off"
    显示重启的次数、总线和仲裁丢失错误,错误主动(error-warning)、错误被动(error-passive)、和总线关闭的状态变化。接收的过载错误在 统计信息的"overrun"域下面列出。

    • 6.5.2 设置CAN的比特时序
    CAN比特时序参数可以使用硬件无关的定义方法。这些参数是: "tq", "prop_seg", "phase_seg1", "phase_seg2" 和 "sjw":

        $ ip link set canX type can tq 125 prop-seg 6 \
                        phase-seg1 7 phase-seg2 2 sjw 1

    在内核选项CONFIG_CAN_CALC_BITTIMING被使能的情况下,如果比特率(波特率)参数 "bitrate"被设置了,CAN的这些参数将会生效:
        
    $ ip link set canX type can bitrate 125000 

    请注意,这条命令在大部分使用标准波特率的CAN控制器上工作良好,但是使用一些少见的波特率值(如115000)和时钟频率值将会失败。禁用内核的CONFIG_CAN_CALC_BITTIMING选项可以节省一些内存空间并且允许用户空间的命令工具完全的控制比特时序参数。使用CAN控制器的比特时序常量就可以达到这个目的(用户空间控制比特时序)。下面的命令将会列出这些变量:

      $ ip -details link show can0 
      ... 
         sja1000: clock 8000000 tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 

    • 6.5.3 启动和停止CAN网络设备

    一个CAN网络设备可以使用"ifconfig canX up/down" 或者 "ip link set canX up/down"来开启和关闭。为了避免错误的默认值,必须在启动CAN设备之前设置它的比特时序参数:

        $ ip link set canX up type can bitrate 125000

    如果总线上出现太多的错误设备可能进入总线关闭状态(也就是从总线脱离)。进入总线关闭状态之后设备不会再发送和接收信息。给"restart-ms"设置一个非零值可以开启总线关闭自动恢复的功能(也就是进入总线关闭状态后重新开启),下面是一个示例:

        $ ip link set canX type can restart-ms 100

    应用程序可以通过监测CAN的错误帧意识到已经进入总线关闭状态,并且可以使用以下命令重启:

        $ ip link set canX type can restart

    注意,重启也会生成一个CAN错误帧(参见3.4节)。

    6.6 支持Socket CAN的硬件
    ------------------------------

    "drivers/net/can"中的“Kconfig”文件中可以查看到所有支持的硬件列表。在CAN套接字项目网站上(参见第7章)有更多驱动何以获得,当然也有对早些时期版本内核的支持 。

    7. 学习Socket CAN的相关资源
    ===================

    你可在BerliOS OSS项目网站的CAN套接字页面中发现更多资源,比如用户空间工具、对旧版内核的支持、更多的驱动、邮件列表等:


    如果你有任何问题或者发现了BUG,不要迟疑,立马发送邮件到CAN套接字的用户邮件列表。但是在发送之前请首先搜索邮件记录中是否已经有了相同的问题。

    8.  贡献者名单
    =========
      
    Oliver Hartkopp (PF_CAN core, filters, drivers, bcm, SJA1000 driver) Urs Thuermann (PF_CAN core, kernel integration, socket interfaces, raw, vcan) Jan Kizka (RT-SocketCAN core, Socket-API reconciliation)
      Wolfgang Grandegger (RT-SocketCAN core & drivers, Raw Socket-API reviews, CAN device driver interface, MSCAN driver) Robert Schwebel (design reviews, PTXdist integration)
      Marc Kleine-Budde (design reviews, Kernel 2.6 cleanups, drivers) Benedikt Spranger (reviews)
      Thomas Gleixner (LKML reviews, coding style, posting hints)
      Andrey Volkov (kernel subtree structure, ioctls, MSCAN driver) Matthias Brukner (first SJA1000 CAN netdevice implementation Q2/2003) Klaus Hitschler (PEAK driver integration)
      Uwe Koppe (CAN netdevices with PF_PACKET approach)
      Michael Schulze (driver layer loopback requirement, RT CAN drivers review) Pavel Pisa (Bit-timing calculation)
      Sascha Hauer (SJA1000 platform driver)
      Sebastian Haas (SJA1000 EMS PCI driver)
      Markus Plessing (SJA1000 EMS PCI driver)
      Per Dalen (SJA1000 Kvaser PCI driver)
      Sam Ravnborg (reviews, coding style, kbuild help) 

    ============================================
    作者:yuanlulu
    http://blog.csdn.net/yuanlulu


    版权没有,但是转载请保留此段声明

    ============================================


    展开全文
  • CAN(Linux内核Socket CAN中文文档) 标签(空格分隔): feng_研究生 内核中Socket CAN的文档,原文地址在: http://lxr.linux.no/linux+v2.6.34/Documentation/networking/can.txt 另外一篇比较好的Socket CAN的...

    CAN(Linux内核Socket CAN中文文档)

    标签(空格分隔): feng_研究生


    内核中Socket CAN的文档,原文地址在:
    http://lxr.linux.no/linux+v2.6.34/Documentation/networking/can.txt

    另外一篇比较好的Socket CAN的英文文档是(详细介绍了广播管理协议套接字):
    Low Level CAN Framework Application Programmers Interface
    http://www.brownhat.org/docs/socketcan/llcf-api.html#SECTION00051000000000000000

    1 概述–什么是Socket CAN?

    2 动机–为什么使用socket API接口?

    3 Socket CAN详解
    3.1 接收队列
    3.2 发送帧的本地回环
    3.3 网络安全相关
    3.4 网络故障监测

    4 如何使用Socket CAN
    4.1 使用can_filter的原始套接字 (RAW socket)
    4.1.1 原始套接字选项 CAN_RAW_FILTER
    4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER
    4.1.3 原始套接字选项 CAN_RAW_LOOPBACK
    4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS
    4.2 广播管理协议套接字 (SOCK_DGRAM)
    4.3 面向连接的传输协议 (SOCK_SEQPACKET)
    4.4 无连接的传输协议 (SOCK_DGRAM)

    5 Socket CAN核心模块
    5.1 can.ko模块的参数
    5.2 procfs接口
    5.3 写一个自己的CAN协议模块

    6 CAN网络驱动
    6.1 常见设置
    6.2 发送帧的本地回环
    6.3 CAN控制器的硬件过滤
    6.4 虚拟的CAN驱动 (vcan)
    6.5 CAN网络设备驱动接口
    6.5.1 Netlink接口–设置/获取设备属性
    6.5.2 设置CAN的比特_时序
    6.5.3 启动和停止CAN网络设备
    6.6 支持Socket CAN的硬件

    7 学习Socket CAN的相关资源

    现在开始=====

    1. 概述–什么是Socket CAN?
      ==================

    socketcan子系统是在Linux下CAN协议(Controller Area Network)实现的一种实现方法。 CAN是一种在世界范围内广泛用于自动控制、嵌入式设备和汽车领域的网络技术。Linux下最早使用CAN的方法是基于字符设备来实现的,与之不同的是Socket CAN使用伯克利的socket接口和linux网络协议栈,这种方法使得can设备驱动可以通过网络接口来调用。Socket CAN的接口被设计的尽量接近TCP/IP的协议,让那些熟悉网络编程的程序员能够比较容易的学习和使用。

    1. 动机–为什么使用socket API接口?
      =======================

    在Socket CAN之前Linux中已经有了一些CAN的实现方法,那为什么还要启动Socket CAN这个项目呢?大多数已经存在的实现方法仅仅作为某个具体硬件的设备驱动,它们往往基于字符设备并且提供的功能很少。那些方案通常是由一个针对具体硬件的设备驱动提供的字符设备接口来实现原始can帧的发送和接收,并且直接和控制器硬件打交道。帧队列和ISO-TP这样的高层协议必须在用户空间来实现。就像串口设备接口一样,大多数基于字符设备的实现在同一时刻仅仅支持一个进程的访问。如果更换了CAN控制器,那么同时也要更换另一个设备驱动,并且需要大多数应用程序重新调整以适应新驱动的API。

    Socket CAN被设计用来克服以上种种不足。这种新的协议族实现了用户空间的socket接口,它构建于Linux网络层之上,因此可以直接使用已有的队列功能。CAN控制器的设备驱动将自己作为一个网络设备注册进Linux的网络层,CAN控制器收到的CAN帧可以传输给高层的网络协议和CAN协议族,反之,发送的帧也会通过高层给CAN控制器。传输协议模块可以使用协议族提供的接口注册自己,所以可以动态的加载和卸载多个传输协议。事实上,CAN核心模块不提供任何协议,也不能在没有加载其它协议的情况下单独使用。同一时间可以在相同或者不同的协议上打开多个套接字,可以在相同或者不同的CAN ID上同时监听和发送(listen/send)。几个同时监听具有相同ID帧的套接字可以在匹配的帧到来后接收到相同的内容。如果一个应用程序希望使用一个特殊的协议(比如ISO-TP)进行通信,只要在打开套接字的时候选择那个协议就可以了,接下来就可以读取和写入应用数据流了,根本无需关心CAN-ID和帧的结构等信息。

    使用字符设备也可以让用户空间获得类似的便利,但是这中解决方案在技术上不够优雅,原因如下:

    *复杂的操作。使用Socket CAN,只需要向socket(2)函数传递协议参数并使用bind(2)选择CAN接口和CAN ID,基于字符设备实现这样的功能却要使用ioctl(2)来完成所有的操作。

    *无法代码复用。字符设备无法使用Linux的网络队列代码,所以必须为CAN 网络重写这部分功能。

    *缺乏抽象性。在大多数已经实现的字符设备方案中,硬件相关的CAN控制器设备驱动直接提供应用程序需要的字符设备。在Unix系统中,无论对于字符设备还是块设备,这都是不常见的。比如,你不会为某个串口、电脑上的某个声卡、访问磁带和硬盘的SCSI/IDE控制器直接创建一个字符设备。相反,你会将向应用程序提供统一字符设备/块设备接口的功能交给一个抽象层来做,你自己仅仅提供硬件相关的设备驱动接口。这种抽象是通过子系统来完成的,比如tty层子系统、声卡子系统和SCSI/IDE子系统。

    实现CAN设备驱动最简单的办法就是直接提供一个字符设备而不使用(或不完全使用)抽象层,大多数已经存在的驱动也是这么做的。但是正确的方法却要增加抽象层以支持各种功能,如注册一个特定的CAN-ID,支持多次打开的操作和这些操作之间的CAN帧复用,支持CAN帧复杂的队列功能,还要提供注册设备驱动的API。然而使用Linux内核提供的网络框架将会大大简化,这就是Socket CAN要做的。

    在Linux中实现CAN功能最自然和合适的方式就是使用内核的网络框架。

    1. Socket CAN详解
      ============

    就像第二章所讲的那样,使用Socket CAN的主要目的就是为用户空间的应用程序提供基于Linux网络层的套接字接口。与广为人知的TCP/IP协议以及以太网不同,CAN总线没有类似以太网的MAC层地址,只能用于广播。CAN ID仅仅用来进行总线的仲裁。因此CAN ID在总线上必须是唯一的。当设计一个CAN-ECU(Electronic Control Unit 电子控制单元)网络的时候,CAN-ID可以映射到具体的ECU。因此CAN-ID可以当作发送源的地址来使用。

    3.1 接收队列

    允许多个应用程序同时访问网络导致了新的问题出现,那就是不同的应用程序可能会在同一个CAN网络接口上对具有相同CAN-ID的帧感兴趣。Socket CAN的核心部分实现了Socket CAN的协议族,通过高效的接收队列解决了这个问题。比如一个用户空间的程序打开了一个原始CAN套接字,原始协议模块将向CAN套接字的核心模块申请用户空间需要的一系列CAN-ID。Socket CAN的核心向CAN协议模块提供预约和解约CAN-ID的接口–can_rx_(un)register(),无论这个CAN-ID是针对一个具体的CAN接口还是所有已知的CAN接口(参考第5章)。

    为了优化CPU的运行效率,每个设备都对应一个接收队列,这样比较容易实现各种报文过滤规则。

    3.2 发送帧的本地回环

    在其它种类的网络中,在相同或者不同网络节点上的应用程序都可以相互交换数据。

         ___   ___   ___                   _______   ___
        | _ | | _ | | _ |                 | _   _ | | _ |
        ||A|| ||B|| ||C||                 ||A| |B|| ||C||
        |___| |___| |___|                 |_______| |___|
          |     |     |                       |       |
        -----------------(1)- CAN bus -(2)---------------
    

    请看上图的两个例子。为了保证应用程序A在两个例子中能够接收到同样的例子(例2中A和B在同一个CAN设备上),发送出去的CAN帧需要能够在本地回环。

    Linux下的网络设备仅仅处理物理媒介上帧的发送和接受。总线仲裁机制下,高优先级的帧会将低优先级的帧延后。为了正确反映实际的通信活动,回环必须在正确传输成功之后进行。如果CAN网络的硬件不支持回环功能,一种低效的方案是使用Socket CAN核心部分来实现软件回环。具体的情况请参考6.2小节。

    CAN网络的回环功能是默认开启的。由于RT-SocketCAN 的特殊需求,每个套接字的回环功能可以被独立关闭。CAN原始套接字的控制选项请参考4.1小节。

    *当你在同一个节点上运行CAN分析命令“candump”或者“cansniffer”的时候就会发现回环功能真的很有用。

    3.3 网络安全相关

    CAN网络是一种现场总线,仅仅支持没有路由和安全控制的广播传输。大部分应用程序都需要要直接处理原始CAN帧,所以和其它类型的网络一样,CAN网络对所有的用户(而不仅仅是root用户)访问没有任何限制。由于当前CAN_RAW和CAN_BCM的实现仅仅支持对CAN接口的读写,所以允许所有的用户访问CAN并不影响其它类型网络的安全性。为了使能非root用户对CAN_RAW和CAN_BCM协议套接字的访问,必须在编译内核的时候选上Kconfig的CAN_RAW_USER/CAN_BCM_USER选项。

    3.4 网络故障监测

    使用CAN总线的时候,可能会遇到物理和mac(media access control)层的问题。为了方便用户分析物理收发器的硬件错误、总线仲裁错误和不同的ECU( Electrical Conversion Unit,电气转换装置)引起的错误,对于底层(物理和mac层)的监测和记录是至关重要的。拥有精确时间戳的错误监测对于诊断错误是非常重要的。基于以上原因,CAN接口的驱动可以可以选择性的产生所谓的错误帧,这些帧以和其它的CAN帧一样的方式传递给用户程序。当一个物理层或者MAC层的错误被(CAN控制器)检测到之后,驱动创建一个相应的错误帧。错误帧可以被应用程序通过CAN的过滤机制请求得到。过滤机制允许选择需要的错误帧的类型。默认情况下,接收错误帧的功能是禁止的。
    CAN错误帧的详细格式定义在linux头文件中:include/linux/can/error.h。

    1. 如何使用Socket CAN
      ===============

    就像TCP/IP协议一样,在使用CAN网络之前你首先需要打开一个套接字。CAN的套接字使用到了一个新的协议族,所以在调用socket(2)这个系统函数的时候需要将PF_CAN作为第一个参数。当前有两个CAN的协议可以选择,一个是原始套接字协议( raw socket protocol),另一个是广播管理协议BCM(broadcast manager)。你可以这样来打开一个套接字:

         s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    或者
         s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
    

    在成功创建一个套接字之后,你通常需要使用bind(2)函数将套接字绑定在某个CAN接口上(这和TCP/IP使用不同的IP地址不同,参见第3章)。在绑定 (CAN_RAW)或连接(CAN_BCM)套接字之后,你可以在套接字上使用read(2)/write(2),也可以使用send(2)/sendto(2)/sendmsg(2)和对应的recv*操作。当然也会有CAN特有的套接字选项,下面将会说明。

    基本的CAN帧结构体和套接字地址结构体定义在include/linux/can.h:

    /*
    * 扩展格式识别符由 29 位组成。其格式包含两个部分:11 位基本 ID、18 位扩展 ID。
    * Controller Area Network Identifier structure
    *
    * bit 0-28     : CAN识别符 (11/29 bit)
    * bit 29     : 错误帧标志 (0 = data frame, 1 = error frame)
    * bit 30     : 远程发送请求标志 (1 = rtr frame)
    * bit 31     :帧格式标志 (0 = standard 11 bit, 1 = extended 29 bit)
    */
    typedef __u32 canid_t;
    
    struct can_frame { 
         canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
          __u8    can_dlc; /* 数据长度: 0 .. 8 */
          __u8    data[8] __attribute__((aligned(8))); 
     };
    

    结构体的有效数据在data[]数组中,它的字节对齐是64bit的,所以用户可以比较方便的在data[]中传输自己定义的结构体和共用体。CAN总线中没有默认的字节序。在CAN_RAW套接字上调用read(2),返回给用户空间的数据是一个struct can_frame结构体。

    就像PF_PACKET套接字一样,sockaddr_can结构体也有接口的索引,这个索引绑定了特定接口:

        struct sockaddr_can { 
              sa_family_t can_family;
              int         can_ifindex;
              union { 
                        /* transport protocol class address info (e.g. ISOTP) */
                        struct { canid_t rx_id, tx_id; } tp;
                        /* reserved for future CAN protocols address information */ 
              } can_addr; 
        };
    

    指定接口索引需要调用ioctl()(比如对于没有错误检查CAN_RAW套接字):

        int s;
        struct sockaddr_can addr;
        struct ifreq ifr;
    
        s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    
        strcpy(ifr.ifr_name, "can0" );
        ioctl(s, SIOCGIFINDEX, &ifr);
    
        addr.can_family = AF_CAN;
        addr.can_ifindex = ifr.ifr_ifindex;
    
        bind(s, (struct sockaddr *)&addr, sizeof(addr));
    
        (..)
    

    为了将套接字和所有的CAN接口绑定,接口索引必须是0。这样套接字便可以从所有使能的CAN接口接收CAN帧。recvfrom(2)可以指定从哪个接口接收。在一个已经和所有CAN接口绑定的套接字上,sendto(2)可以指定从哪个接口发送。

    从一个CAN_RAW套接字上读取CAN帧也就是读取struct can_frame结构体:

        struct can_frame frame;
    
        nbytes = read(s, &frame, sizeof(struct can_frame));
    
        if (nbytes < 0) {
                perror("can raw socket read");
                return 1;
        }
    
        /* paranoid check ... */
        if (nbytes < sizeof(struct can_frame)) {
                fprintf(stderr, "read: incomplete CAN frame\n");
                return 1;
        }
    
        /* do something with the received CAN frame */
    

    写CAN帧也是类似的,需要用到write (2)函数:

        nbytes = write(s, &frame, sizeof(struct can_frame)); 
    

    如果套接字跟所有的CAN接口都绑定了(addr.can_index = 0),推荐使用recvfrom(2)获取数据源接口的信息:

        struct sockaddr_can addr;
        struct ifreq ifr;
        socklen_t len = sizeof(addr);
        struct can_frame frame;
    
        nbytes = recvfrom(s, &frame, sizeof(struct can_frame),
                          0, (struct sockaddr*)&addr, &len);
    
        /* get interface name of the received CAN frame */
        ifr.ifr_ifindex = addr.can_ifindex;
        ioctl(s, SIOCGIFNAME, &ifr);
        printf("Received a CAN frame from interface %s", ifr.ifr_name);
    

    对于绑定了所有接口的套接字,向某个端口发送数据必须指定接口的详细信息:

        strcpy(ifr.ifr_name, "can0");
        ioctl(s, SIOCGIFINDEX, &ifr);
        addr.can_ifindex = ifr.ifr_ifindex;
        addr.can_family  = AF_CAN;
    
        nbytes = sendto(s, &frame, sizeof(struct can_frame),
                        0, (struct sockaddr*)&addr, sizeof(addr));
    

    4.1 使用can_filter的原始套接字 (RAW socket)

    CAN_RAW套接字的用法和CAN字符设备的用法是类似的。为了使用CAN套接字的新特性,在绑定原始套接字的时候将会默认开启以下特性:

    • filter将会接收所有的数据
    • 套接字仅仅接收有效的数据帧(=> no error frames)
    • 发送帧的回环功能被开启(参见 3.2节)
    • (回环模式下)套接字不接收它自己发送的帧

    这些特性的设置可以在绑定之前和之后修改。为了使用CAN_RAW套接字相关的选项,必须包含<linux/can/raw.h>。

    4.1.1 原始套接字选项 CAN_RAW_FILTER

    CAN_RAW套接字的接收可以使用CAN_RAW_FILTER套接字选项指定的多个过滤规则(过滤器)来过滤。

    过滤规则(过滤器)的定义在 include/linux/can.h中:

        struct can_filter {
                canid_t can_id;
                canid_t can_mask;
        };
    

    过滤规则的匹配:

        <received_can_id> & mask == can_id & mask
    
    /*
    #define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
    #define CAN_ERR_FLAG   0x20000000U /* error frame */
    */
    

    这和大家所熟知的CAN控制器硬件过滤非常相似。可以使用 CAN_INV_FILTER这个宏将can_filter结构体的成员can_id中的比特位反转。和CAN控制器的硬件过滤形成鲜明对比的是,用户可以为每一个打开的套接字设置多个独立的过滤规则(过滤器):

    /*
     /* valid bits in CAN ID for frame formats */
    #define CAN_SFF_MASK 0x000007FFU /* 标准帧格式 (SFF) */
    #define CAN_EFF_MASK 0x1FFFFFFFU /* 扩展帧格式 (EFF) */
    #define CAN_ERR_MASK 0x1FFFFFFFU /* 忽略EFF, RTR, ERR标志 */
     */
    
        struct can_filter rfilter[2];
    
        rfilter[0].can_id   = 0x123;
        rfilter[0].can_mask = CAN_SFF_MASK;
        rfilter[1].can_id   = 0x200;
        rfilter[1].can_mask = 0x700;
    
        setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
    

    为了在指定的CAN_RAW套接字上禁用接收过滤规则,可以这样:

      setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
    

    在一些极端情况下不需要读取数据,可以把过滤规则清零(所有成员设为0),这样原始套接字就会忽略接收到的CAN帧。在这种仅仅发送数据(不读取)的应用中可以在内核中省略接收队列,以此减少CPU的负载(虽然只能减少一点点)。

    4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER

    正如3.4节所说,CAN接口驱动可以选择性的产生错误帧,错误帧和正常帧以相同的方式传给应用程序。可能产生的错误被分为不同的种类,使用适当的错误掩码可以过滤它们。为了注册所有可能的错误情况,CAN_ERR_MASK(0x1FFFFFFFU)这个宏可以用来作为错误掩码。这个错误掩码定义在linux/can/error.h。

        can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );
    
        setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER,
                   &err_mask, sizeof(err_mask));
    

    4.1.3 原始套接字选项 CAN_RAW_LOOPBACK

    为了满足众多应用程序的需要,本地回环功能默认是开启的(详细情况参考3.2节)。但是在一些嵌入式应用场景中(比如只有一个用户在使用CAN总线),回环功能可以被关闭(各个套接字之间是独立的):

        int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
    
        setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
    

    4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS

    在本地回环功能开启的情况下,所有的发送帧都会被回环到在相应CAN接口上注册了同样CAN-ID(和发送帧的相同)的套接字上。发送CAN帧的套接字被假设不想接收自己发送的CAN帧,因此在发送套接字上的回环功能默认是关闭的。可以在需要的时候改变这一默认行为:

        int recv_own_msgs = 1; /* 0 = disabled (default), 1 = enabled */
    
        setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
                   &recv_own_msgs, sizeof(recv_own_msgs));
    

    (yll:下面三小节没有内容)
    4.2 广播管理协议套接字 (SOCK_DGRAM)

    4.3 面向连接的传输协议 (SOCK_SEQPACKET)

    4.4 无连接的传输协议 (SOCK_DGRAM)

    1. Socket CAN核心模块
      ==============

    CAN套接字的核心模块实现了PF_CAN协议族。CAN协议模块在核心模块运行后加载。CAN核心模块为协议模块提供了申请需要的ID的接口(参考3.1小节)。

    5.1 can.ko模块的参数

    • stats_timer: 为了计算CAN套接字的统计信息(比如最近一秒的帧数和每秒最大的帧数),can.ko调用一个定时间隔为1秒的定时器,默认情况下这个定时器是开启的。这个定时器也可以在模块参数中传入stattimer=0来禁止。

    • debug : (removed since SocketCAN SVN r546)

    5.2 procfs接口

    就像3.1节描述的那样,CAN套接字核心借助于一些带有过滤规则的队列向CAN协议模块传递接收到的CAN帧。可以在procfs中查看这些接收队列的的过滤规则和匹配规则的次数。所有的条目都包了设备名和协议模块标识:

        foo@bar:~$ cat /proc/net/can/rcvlist_all
        
        receive list 'rx_all': (vcan3: no entry)
          (vcan2: no entry)
          (vcan1: no entry)
          device   can_id   can_mask  function  userdata   matches  ident
          vcan0     000    00000000  f88e6370  f6c6f400         0       raw
         (any: no entry)
         (yll补充:function是一个函数指针,userdata是一个void *指针,所以这两个值打印出来没有太大意义)
    

    在这个例子中,这个应用程序接收所有vcan0上传输的数据.
    rcvlist_all - 没有过滤规则的队列 rcvlist_eff - 扩展帧(EFF)的队列 rcvlist_err - 错误帧队列 rcvlist_fil - 通过过滤规则的队列 rcvlist_inv - 未通过过滤规则的队列 rcvlist_sff - 标准帧的队列

    在/proc/net/can中还有另外一些文件:

    stats - CAN套接字核心的统计信息(接收/发送的帧数,匹配率等) reset_stats - 复位统计信息 version - 打印CAN套接字核心的版本和ABI的版本

    5.3 写一个自己的CAN协议模块

    要在PF_CAN中增加一个新的协议,必须在include/linux/can.h中为新的协议增加相应的定义。包含include/linux/can.h这个文件便可以使用增加的原型和定义。内核除了提供了注册CAN协议和CAN设备的通知列表的功能,也提供了在一个特定CAN接口上注册感兴趣的CAN帧或者发送CAN帧的功能。

    can_rx_register - 在一个特定接口上注册希望接收到的CAN帧的信息 (yll:这个函数的定义在内核的net/can/af_can.c中)
    can_rx_unregister - 注销上面的申请
    can_send - 发送CAN帧(可以选择是否开启本地回环)

    详细的信息请参考内核中的源码net/can/af_can.c、net/can/raw.c、net/can/bcm.c

    1. CAN网络驱动
      ==========
      编写一个CAN网络设备驱动要比写一个CAN字符设备驱动要容易的多。和编写其它网络设备驱动类似,你只要处理以下事宜:
    • TX :将套接字缓冲区的CAN帧发送到CAN控制器
    • RX :从CAN控制器的CAN帧读取到套接字缓冲区

    Documentation/networking/netdevices.txt中是网络设备驱动的例子。下面将会介绍CAN网络设备驱动的一些独有特性。

    6.1 常见设置

        dev->type  = ARPHRD_CAN; /* the netdevice hardware type */
        dev->flags = IFF_NOARP;  /* CAN has no arp */
        dev->mtu   = sizeof(struct can_frame);
    

    结构体can_frame是PF_CAN协议族套接字缓冲区的数组载体。

    6.2 发送帧的本地回环

    如3.2小节所述,CAN网络设备驱动应该支持类似TTY设备回显功能的本地回环功能。如果驱动支持这个功能,则要设备IFF_ECHO标志来防止PF_CAN核心回显发送帧(又称回环)。

        dev->flags = (IFF_NOARP | IFF_ECHO);
    

    6.3 CAN控制器的硬件过滤

    为了减小一些嵌入式系统的中断负载,一些CAN控制器支持多个CAN-ID或者多个CAN-ID区间的过滤功能。硬件过滤功能在不同的控制器之间差异很大,并且不能同时满足多个用户的不同过滤需求。在单用户应用中使用控制器的硬件过滤或许还有意义,但是在一个多用户系统中驱动层的过滤将会影响所有用户。PF_CAN核心内置的过滤规则集合允许对每个套接字独立的设置多个过滤规则。因此使用硬件过滤属于嵌入式系统中“手动调整”的范畴。从2002年开始笔者一直使用拥有四路SJA1000 CAN控制器的MPC603e @133MHz,总线负载虽然很高,但是到目前为止还没有什么问题。

    6.4 虚拟的CAN驱动 (vcan)

    和网络回环设备一样,vcan提供一个虚拟的本地CAN接口。CAN中一个有效的地址包括:

    • 一个有效的CAN标识符(CAN ID)
    • 这个CAN标识符将要发往的总线(比如 can0).

    所以在一般的应用场景中往往需要多个vcan接口。

    vcan接口允许在没有控制器硬件的情况下进行发送和接收。vcan网络设备的命名一般采用‘vcanX’,比如can1 vcan2等。当编译为单独的模块的时候,vcan驱动的模块名为vcan.ko。

    vcan驱动从linux2.6.24开始支持netlink接口,使得创建vcan网络设备变的可能。可以使用ip(8)命令工具管理vcan网络设备的创建和移除:

    • 创建一个vcan网络接口:
      $ ip link add type vcan

    • 使用给定的名字 'vcan42’创建 一个vcan网络接口:
      $ ip link add dev vcan42 type vcan

    • 移除vcan网络接口’vcan42’:
      $ ip link del vcan42

    6.5 CAN网络设备驱动接口

    CAN网络设备驱动提供了进行安装、配置和监控CAN网络设备的接口。可以使用IPROUTE2工具集中的“ip”命令通过netlink接口配置CAN设备,比如设置波特率。本章剩余的部分将会简介如何使用这一工具。另外这些接口使用一些通用的数据结构并且提供了一系列常用的功能,这些功能都是CAN网络设备驱动需要用到的。请参考SJA1000或者MSCAN的驱动去了解如何使用它们。模块的名字是can-dev.ko。

    6.5.1 Netlink接口--设置/获取设备属性
    

    CAN设备必须使用netlink来配置。在"include/linux/can/netlink.h"有对netlink消息类型的定义和简短描述。IPROUTE2工具集中的“ip”命令可以使用CAN的netlink支持,下面是使用示例:

    • 设置CAN设备属性:
        $ ip link set can0 type can help
        Usage: ip link set DEVICE type can [ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |
        [ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1 phase-seg2 PHASE-SEG2 [ sjw SJW ] ]
        [ loopback { on | off } ]
        [ listen-only { on | off } ]
        [ triple-sampling { on | off } ]
        [ restart-ms TIME-MS ]
        [ restart ]
        Where: BITRATE       := { 1..1000000 } SAMPLE-POINT  := { 0.000..0.999 }
              TQ            := { NUMBER }
              PROP-SEG      := { 1..8 }
              PHASE-SEG1    := { 1..8 }
              PHASE-SEG2    := { 1..8 }
              SJW           := { 1..4 }
              RESTART-MS    := { 0 | NUMBER }
    
    • 显示CAN设备的详情和统计信息:
        $ ip -details -statistics link show can0
     2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP qlen 10 
         link/can
          can <TRIPLE-SAMPLING> state ERROR-ACTIVE restart-ms 100
          bitrate 125000 sample_point 0.875
          tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
          sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
          clock 8000000
          re-started bus-errors arbit-lost error-warn error-pass bus-off 
          41         17457      0          41         42         41
          RX: bytes  packets  errors  dropped overrun mcast 
          140859     17608    17457   0       0       0
          TX: bytes  packets  errors  dropped carrier collsns 
          861        112      0       41      0       0
    

    下面是上面一些名词的解释:

    "<TRIPLE-SAMPLING>"
    

    表示选中的CAN控制器的模式:LOOPBACK, LISTEN-ONLY, or TRIPLE-SAMPLING。

    "state ERROR-ACTIVE"
    

    CAN控制器的当前状态:“ERROR-ACTIVE”, “ERROR-WARNING”, “ERROR-PASSIVE”, “BUS-OFF” or “STOPPED”

    "restart-ms 100"
    

    自动重启的延时时间。如果设为非零值, 在总线关闭的情况下,在设定的数量毫秒后CAN控制器被自动触发。这个功能默认是关闭的。

    "bitrate 125000 sample_point 0.875"
    

    使用bits/sec作为单位显示位时间并显示0.000~0.999的采样点位置。如果内核中使能了统计位时间的功能(CONFIG_CAN_CALC_BITTIMING=y),位时间可以使用"bitrate"参数来设置。可选的"sample-point"也是可以配置的。默认使用0.000这个推荐值。

    "tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1"
    

    以ns为单位显示时间份额(tq-time quanta)、传播段(prop-seg : propagation segment)、相位缓冲段1和2(phase-seg:phase buffer),以tq为单位显示同步跳转宽度(sjw:synchronisation jump width)。这些变量允许定义与硬件无关的位时序,这也是Bosch CAN 2.0 spec所推荐的(参考第八章http://www.semiconductors.bosch.de/pdf/can2spec.pdf)。

    "sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 clock 8000000"
    

    显示CAN控制器的比特时序常量,这里的例子以sja1000为例。时间段(tseg -time segment)1和2的最小和最大值,以tq为单位的同步跳转宽度,比特速率的预分频器(brp–pre-scaler)和CAN系统时钟(以HZ为单位)。这些常量可以被用户空间的比特时序统计算法所使用。

    "re-started bus-errors arbit-lost error-warn error-pass bus-off"
    

    显示重启的次数、总线和仲裁丢失错误,错误主动(error-warning)、错误被动(error-passive)、和总线关闭的状态变化。接收的过载错误在 统计信息的"overrun"域下面列出。

    6.5.2 设置CAN的比特时序
    

    CAN比特时序参数可以使用硬件无关的定义方法。这些参数是: “tq”, “prop_seg”, “phase_seg1”, “phase_seg2” 和 “sjw”:

    $ ip link set canX type can tq 125 prop-seg 6 \
                    phase-seg1 7 phase-seg2 2 sjw 1
    

    在内核选项CONFIG_CAN_CALC_BITTIMING被使能的情况下,如果比特率(波特率)参数 "bitrate"被设置了,CAN的这些参数将会生效:

    $ ip link set canX type can bitrate 125000

    请注意,这条命令在大部分使用标准波特率的CAN控制器上工作良好,但是使用一些少见的波特率值(如115000)和时钟频率值将会失败。禁用内核的CONFIG_CAN_CALC_BITTIMING选项可以节省一些内存空间并且允许用户空间的命令工具完全的控制比特时序参数。使用CAN控制器的比特时序常量就可以达到这个目的(用户空间控制比特时序)。下面的命令将会列出这些变量:

    $ ip -details link show can0

    sja1000: clock 8000000 tseg1 1…16 tseg2 1…8 sjw 1…4 brp 1…64 brp-inc 1

    6.5.3 启动和停止CAN网络设备
    

    一个CAN网络设备可以使用"ifconfig canX up/down" 或者 "ip link set canX up/down"来开启和关闭。为了避免错误的默认值,必须在启动CAN设备之前设置它的比特时序参数:

    $ ip link set canX up type can bitrate 125000
    

    如果总线上出现太多的错误设备可能进入总线关闭状态(也就是从总线脱离)。进入总线关闭状态之后设备不会再发送和接收信息。给"restart-ms"设置一个非零值可以开启总线关闭自动恢复的功能(也就是进入总线关闭状态后重新开启),下面是一个示例:

    $ ip link set canX type can restart-ms 100
    

    应用程序可以通过监测CAN的错误帧意识到已经进入总线关闭状态,并且可以使用以下命令重启:

    $ ip link set canX type can restart
    

    注意,重启也会生成一个CAN错误帧(参见3.4节)。

    6.6 支持Socket CAN的硬件

    "drivers/net/can"中的“Kconfig”文件中可以查看到所有支持的硬件列表。在CAN套接字项目网站上(参见第7章)有更多驱动何以获得,当然也有对早些时期版本内核的支持 。

    1. 学习Socket CAN的相关资源
      ===================

    你可在BerliOS OSS项目网站的CAN套接字页面中发现更多资源,比如用户空间工具、对旧版内核的支持、更多的驱动、邮件列表等:

    http://developer.berlios.de/projects/socketcan
    
    展开全文
  •  Linux内核Socket参数调优  可调优的内核变量存在两种主要接口:sysctl命令和/proc文件系统,proc中与进程无关的所有信息都被移植到sysfs中。IPV4协议栈的sysctl参数主要是sysctl.net.core、sysctl.net.

    原文:http://blog.chinaunix.net/uid-9688646-id-4049599.html


     Linux内核Socket参数调优 

    可调优的内核变量存在两种主要接口:sysctl命令和/proc文件系统,proc中与进程无关的所有信息都被移植到sysfs中。IPV4协议栈的sysctl参数主要是sysctl.net.core、sysctl.net.ipv4,对应的/proc文件系统是/proc/sys/net/ipv4和/proc/sys/net/core。只有内核在编译时包含了特定的属性,该参数才会出现在内核中。

        对于内核参数应该谨慎调节,这些参数通常会影响到系统的整体性能。内核在启动时会根据系统的资源情况来初始化特定的变量,这种初始化的调节一般会满足通常的性能需求。

        应用程序通过socket系统调用和远程主机进行通讯,每一个socket都有一个读写缓冲区。读缓冲区保存了远程主机发送过来的数据,如果缓冲区已满,则数据会被丢弃,写缓冲期保存了要发送到远程主机的数据,如果写缓冲区已慢,则系统的应用程序在写入数据时会阻塞。可知,缓冲区是有大小的。

    socket缓冲区默认大小
    /proc/sys/net/core/rmem_default     对应net.core.rmem_default
    /proc/sys/net/core/wmem_default     对应net.core.wmem_default
        上面是各种类型socket的默认读写缓冲区大小,然而对于特定类型的socket则可以设置独立的值覆盖默认值大小。例如tcp类型的socket就可以用/proc/sys/net/ipv4/tcp_rmem和tcp_wmem来覆盖。

    socket缓冲区最大值:
    /proc/sys/net/core/rmem_max        对应net.core.rmem_max
    /proc/sys/net/core/wmem_max        对应net.core.wmem_max

    /proc/sys/net/core/netdev_max_backlog    对应 net.core.netdev_max_backlog
        该参数定义了当接口收到包的速率大于内核处理包的速率时,设备的输入队列中的最大报文数。

    /proc/sys/net/core/somaxconn        对应 net.core.somaxconn
        通过listen系统调用可以指定的最大accept队列backlog,当排队的请求连接大于该值时,后续进来的请求连接会被丢弃。

    /proc/sys/net/core/optmem_max          对应 net.core.optmem_max
        每个socket的副缓冲区大小。

    TCP/IPV4内核参数:
        在创建socket的时候会指定socke协议和地址类型。TCP socket缓冲区大小是他自己控制而不是由core内核缓冲区控制。
    /proc/sys/net/ipv4/tcp_rmem     对应net.ipv4.tcp_rmem
    /proc/sys/net/ipv4/tcp_wmem     对应net.ipv4.tcp_wmem
        以上是TCP socket的读写缓冲区的设置,每一项里面都有三个值,第一个值是缓冲区最小值,中间值是缓冲区的默认值,最后一个是缓冲区的最大值,虽然缓冲区的值不受core缓冲区的值的限制,但是缓冲区的最大值仍旧受限于core的最大值。

    /proc/sys/net/ipv4/tcp_mem  
        该内核参数也是包括三个值,用来定义内存管理的范围,第一个值的意思是当page数低于该值时,TCP并不认为他为内存压力,第二个值是进入内存的压力区域时所达到的页数,第三个值是所有TCP sockets所允许使用的最大page数,超过该值后,会丢弃后续报文。page是以页面为单位的,为系统中socket全局分配的内存容量。

    socket的结构如下图:


    /proc/sys/net/ipv4/tcp_window_scaling      对应net.ipv4.tcp_window_scaling
        管理TCP的窗口缩放特性,因为在tcp头部中声明接收缓冲区的长度为26位,因此窗口不能大于64K,如果大于64K,就要打开窗口缩放。

    /proc/sys/net/ipv4/tcp_sack    对应net.ipv4.tcp_sack
        管理TCP的选择性应答,允许接收端向发送端传递关于字节流中丢失的序列号,减少了段丢失时需要重传的段数目,当段丢失频繁时,sack是很有益的。

    /proc/sys/net/ipv4/tcp_dsack   对应net.ipv4.tcp_dsack
        是对sack的改进,能够检测不必要的重传。

    /proc/sys/net/ipv4/tcp_fack    对应net.ipv4.tcp_fack
        对sack协议加以完善,改进tcp的拥塞控制机制。

    TCP的连接管理:
    /proc/sys/net/ipv4/tcp_max_syn_backlog    对应net.ipv4.tcp_max_syn_backlog
        每一个连接请求(SYN报文)都需要排队,直至本地服务器接收,该变量就是控制每个端口的 TCP SYN队列长度的。如果连接请求多余该值,则请求会被丢弃。

    /proc/sys/net/ipv4/tcp_syn_retries    对应net.ipv4.tcp_syn_retries
        控制内核向某个输入的SYN/ACK段重新发送相应的次数,低值可以更好的检测到远程主机的连接失败。可以修改为3

    /proc/sys/net/ipv4/tcp_retries1    对应net.ipv4.tcp_retries1
        该变量设置放弃回应一个tcp连接请求前,需要进行多少次重试。

    /proc/sys/net/ipv4/tcp_retries2    对应net.ipv4.tcp_retries2
        控制内核向已经建立连接的远程主机重新发送数据的次数,低值可以更早的检测到与远程主机失效的连接,因此服务器可以更快的释放该连接,可以修改为5

    TCP连接的保持:
    /proc/sys/net/ipv4/tcp_keepalive_time        对应net.ipv4.tcp_keepalive_time
        如果在该参数指定的秒数内连接始终处于空闲状态,则内核向客户端发起对该主机的探测

    /proc/sys/net/ipv4/tcp_keepalive_intvl    对应net.ipv4.tcp_keepalive_intvl
        该参数以秒为单位,规定内核向远程主机发送探测指针的时间间隔

    /proc/sys/net/ipv4/tcp_keepalive_probes   对应net.ipv4.tcp_keepalive_probes
        该参数规定内核为了检测远程主机的存活而发送的探测指针的数量,如果探测指针的数量已经使用完毕仍旧没有得到客户端的响应,即断定客户端不可达,关闭与该客户端的连接,释放相关资源。

    /proc/sys/net/ipv4/ip_local_port_range   对应net.ipv4.ip_local_port_range
        规定了tcp/udp可用的本地端口的范围。

    TCP连接的回收:
    /proc/sys/net/ipv4/tcp_max_tw_buckets     对应net.ipv4.tcp_max_tw_buckets
       该参数设置系统的TIME_WAIT的数量,如果超过默认值则会被立即清除。

    /proc/sys/net/ipv4/tcp_tw_reuse           对应net.ipv4.tcp_tw_reuse
       该参数设置TIME_WAIT重用,可以让处于TIME_WAIT的连接用于新的tcp连接

    /proc/sys/net/ipv4/tcp_tw_recycle         对应net.ipv4.tcp_tw_recycle
       该参数设置tcp连接中TIME_WAIT的快速回收。

    /proc/sys/net/ipv4/tcp_fin_timeout       对应net.ipv4.tcp_fin_timeout
       设置TIME_WAIT2进入CLOSED的等待时间。

    /proc/sys/net/ipv4/route/max_size
       内核所允许的最大路由数目。

    /proc/sys/net/ipv4/ip_forward
       接口间转发报文

    /proc/sys/net/ipv4/ip_default_ttl
       报文可以经过的最大跳数

    虚拟内存参数:
    /proc/sys/vm/


       在linux kernel 2.6.25之前通过ulimit -n(setrlimit(RLIMIT_NOFILE))设置每个进程的最大打开文件句柄数不能超过NR_OPEN(1024*1024),也就是100多w(除非重新编译内核),而在25之后,内核导出了一个sys接口可以修改这个最大值/proc/sys/fs/nr_open。shell里不能直接更改,是因为登录的时候pam已经从limits.conf中设置了上限,ulimit命令只能在低于上限的范围内发挥了。

    Linux中查看socket状态:
    cat /proc/net/sockstat #(这个是ipv4的)

    sockets: used 137
    TCP: inuse 49 orphan 0 tw 3272 alloc 52 mem 46
    UDP: inuse 1 mem 0
    RAW: inuse 0
    FRAG: inuse 0 memory 0
    说明:
    sockets: used:已使用的所有协议套接字总量
    TCP: inuse:正在使用(正在侦听)的TCP套接字数量。其值≤ netstat –lnt | grep ^tcp | wc –l
    TCP: orphan:无主(不属于任何进程)的TCP连接数(无用、待销毁的TCP socket数)
    TCP: tw:等待关闭的TCP连接数。其值等于netstat –ant | grep TIME_WAIT | wc –l
    TCP:alloc(allocated):已分配(已建立、已申请到sk_buff)的TCP套接字数量。其值等于netstat –ant | grep ^tcp | wc –l
    TCP:mem:套接字缓冲区使用量(单位不详。用scp实测,速度在4803.9kB/s时:其值=11,netstat –ant 中相应的22端口的Recv-Q=0,Send-Q≈400)
    UDP:inuse:正在使用的UDP套接字数量
    RAW:
    FRAG:使用的IP段数量


    参考:http://www.mjmwired.net/kernel/Documentation/sysctl/


    展开全文
  • Fastsocket 是一个高扩展性的 Socket 以及 Linux 内核的底层网络实现。可以在多核机器上提供极好的性能,此外使用和维护还非常简单
  • linux内核socket实现

    千次阅读 2017-02-06 22:32:26
    1.2 sock_map_fd函数 ... 在用户空间创建了一个socket后,返回值是一个文件描述符,下面分析一下创建socket时怎么和文件描述符联系的。在SYSCALL_DEFINE3(socket, int, family, int, type,
  • Linux kernel下的内核socket编程

    千次阅读 2018-04-11 17:02:44
    内核4版本下测试通过,引用了高手的代码,做了修改,避免崩溃:...lt;linux/in.h&gt; #...
  • 2014年10月18日,当时就职于新浪操作系统团队的林晓峰在Github上开源了名为Fastsocket的项目,并在之后...高度可扩展的socket 是Linux内核层面的底层网络实现 在多核机器上可实现极佳性能,24核以内的性能增长呈线性
  • net.core.netdev_max_backlog = 30000 每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目net.core.somaxconn = 262144 用来限制监听(LISTEN)队列最大数据包的数量,超过...
  • android内核Socket CAN中文译文

    千次阅读 2014-09-22 10:41:57
    =====================================================...这篇文章主要针对can协议簇(aka socket can) 这篇文章包含以下内容: =============== 1 概述--什么是Socket CAN? 2 动机--为什么使用so
  • 2013年10月08日 09:45:38阅读数:13193Linux:vi /etc/sysctl.confnet.core.netdev_max_backlog = 30000 每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目...
  • 我试使用下面的流程发送TCP包的 sock_create_kern sock->ops->connect kernel_sendmsg 我想改成非阻塞的模式,请问要用什么方法
  • 内核Socket TCP编程

    2017-04-18 17:19:35
    1.内核socket API 内核socket编程的过程和用户态下的socket编程流程一样,但是接口不同。Kernel提供了一组内核态的socket API,基本上在用户态的sockt API在内核中都有对应的API。 在net/socket.c中可以看到...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,504
精华内容 2,601
关键字:

内核socket