一、candump源码解析
#include <can_config.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <stdint.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <linux/can.h>
#include <linux/can/raw.h>
extern int optind, opterr, optopt;
static int s = -1;
static int running = 1;
enum {
VERSION_OPTION = CHAR_MAX + 1,
FILTER_OPTION,
};
static void print_usage(char *prg)
{
fprintf(stderr, "Usage: %s [<can-interface>] [Options]\n"
"Options:\n"
" -f, --family=FAMILY\t" "protocol family (default PF_CAN = %d)\n"//指定协议族
" -t, --type=TYPE\t" "socket type, see man 2 socket (default SOCK_RAW = %d)\n"//指定socket类型
" -p, --protocol=PROTO\t" "CAN protocol (default CAN_RAW = %d)\n"//指定特殊协议
" --filter=id:mask[:id:mask]...\n"//指定id和验收屏蔽
"\t\t\t" "apply filter\n"
" -h, --help\t\t" "this help\n"//使用说明 这里注释错误,-h无法被解析请使用长选项
" -o <filename>\t\t" "output into filename\n" //将输出重定向到文件
" -d\t\t\t" "daemonize\n"//后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
" --version\t\t" "print version information and exit\n",//查看版本号
prg, PF_CAN, SOCK_RAW, CAN_RAW);
}
static void sigterm(int signo)
{
running = 0;
}
static struct can_filter *filter = NULL;
static int filter_count = 0;
#if 0
/usr/include/linux/can.h
/**
* struct can_filter - CAN ID based filter in can_register().
* @can_id: relevant bits of CAN ID which are not masked out.没有被屏蔽的CAN ID的相关位
* @can_mask: CAN mask (see description)
*
* Description:
* A filter matches, when
*
* <received_can_id> & mask == can_id & mask
*
* The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
* filter for error message frames (CAN_ERR_FLAG bit set in mask).
*/过滤器可以倒置(CAN_INV_FILTER位设置在can_id中)或它可以过滤错误消息帧(CAN_ERR_FLAG位设置在mask中)。
struct can_filter {
canid_t can_id;
canid_t can_mask;
};
#endif
int add_filter(u_int32_t id, u_int32_t mask)
{
filter = realloc(filter, sizeof(struct can_filter) * (filter_count + 1));//动态调整内存,
if(!filter)
return -1;
filter[filter_count].can_id = id;
filter[filter_count].can_mask = mask;
filter_count++;
printf("id: 0x%08x mask: 0x%08x\n",id,mask);
return 0;
}
#define BUF_SIZ (255)
int main(int argc, char **argv)
{
//下面这三个结构体在cansend解析中分析过
struct can_frame frame;
struct ifreq ifr;
struct sockaddr_can addr;
FILE *out = stdout;
char *interface = "can0";
char *optout = NULL;
char *ptr;
char buf[BUF_SIZ];
int family = PF_CAN, type = SOCK_RAW, proto = CAN_RAW;
int n = 0, err;
int nbytes, i;
int opt, optdaemon = 0;
uint32_t id, mask;
signal(SIGPIPE, SIG_IGN);
struct option long_options[] = {// 长选项
{ "help", no_argument, 0, 'h' },//--help不需要参数
{ "family", required_argument, 0, 'f' },
{ "protocol", required_argument, 0, 'p' },
{ "type", required_argument, 0, 't' },
{ "filter", required_argument, 0, FILTER_OPTION },//--filter需要参数
{ "version", no_argument, 0, VERSION_OPTION},//--version不需要参数
{ 0, 0, 0, 0},
};
//短选项-f -t -p -o 都必须使用参数 -d不需要参数
while ((opt = getopt_long(argc, argv, "f:t:p:o:d", long_options, NULL)) != -1) {
switch (opt) {
case 'd':
optdaemon++; //后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
break;
case 'h'://打印使用说明
print_usage(basename(argv[0]));
exit(0);
case 'f'://指定协议族
family = strtoul(optarg, NULL, 0);
break;
case 't'://执行协议类型
type = strtoul(optarg, NULL, 0);
break;
case 'p'://指定特殊协议
proto = strtoul(optarg, NULL, 0);
break;
case 'o'://指定保存输出信息的文件
optout = optarg;
break;
case FILTER_OPTION: //指定id和mask 将其保存到can_filter
ptr = optarg;
while(1) {
id = strtoul(ptr, NULL, 0);//解析ID
ptr = strchr(ptr, ':');//得到“:”字符地址
if(!ptr) {
fprintf(stderr, "filter must be applied in the form id:mask[:id:mask]...\n");
exit(1);
}
ptr++;//得到“:”字符后mask字符首地址
mask = strtoul(ptr, NULL, 0);//得到mask
ptr = strchr(ptr, ':');//同上,如果存在第二个: 说明是双滤波
add_filter(id,mask);//把得到的id和mask添加到can_filter结构体
if(!ptr)//说明没有后续的id和mask
break;
ptr++;//得到第二个id字符首地址
}
break;
case VERSION_OPTION://版本号
printf("candump %s\n",VERSION);
exit(0);
default:
fprintf(stderr, "Unknown option %c\n", opt);
break;
}
}
if (optind != argc) //代表指定了设备
interface = argv[optind];//第一个不是选项的参数就是设备名,
printf("interface = %s, family = %d, type = %d, proto = %d\n",
interface, family, type, proto);
if ((s = socket(family, type, proto)) < 0) {
perror("socket");
return 1;
}
addr.can_family = family;
strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
if (ioctl(s, SIOCGIFINDEX, &ifr)) {//获取接口索引,需要通过name从网络名字空间进行匹配因此需要提前设置好name
perror("ioctl");
return 1;
}
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
return 1;
}
if (filter) {
//SOL_CAN_RAW的用法 见官方文档./Documentation/networking/can.rst
//此滤波为软件滤波,因此可以设置任意个
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, filter,//设置接受滤波器
filter_count * sizeof(struct can_filter)) != 0) {
perror("setsockopt");
exit(1);
}
}
if (optdaemon)
daemon(1, 0);//后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
else {
signal(SIGTERM, sigterm);//更改信号处理函数用来停止接收
signal(SIGHUP, sigterm);
}
if (optout) {
out = fopen(optout, "a");
if (!out) {
perror("fopen");
exit (EXIT_FAILURE);
}
}
#if 0
/**
* struct can_frame - basic CAN frame structure
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @can_dlc: frame payload length in byte (0 .. 8) aka data length code
* N.B. the DLC field from ISO 11898-1 Chapter 8.4.2.3 has a 1:1
* mapping of the 'data length code' to the real payload length
* @__pad: padding
* @__res0: reserved / padding
* @__res1: reserved / padding
* @data: CAN frame payload (up to 8 byte)
*/
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
__u8 __pad; /* padding */
__u8 __res0; /* reserved / padding */
__u8 __res1; /* reserved / padding */
__u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));
};
#endif
while (running) {
if ((nbytes = read(s, &frame, sizeof(struct can_frame))) < 0) {
perror("read");
return 1;
} else {
if (frame.can_id & CAN_EFF_FLAG)//扩展帧
n = snprintf(buf, BUF_SIZ, "<0x%08x> ", frame.can_id & CAN_EFF_MASK);
else//标准帧
n = snprintf(buf, BUF_SIZ, "<0x%03x> ", frame.can_id & CAN_SFF_MASK);
n += snprintf(buf + n, BUF_SIZ - n, "[%d] ", frame.can_dlc);//数据长度
for (i = 0; i < frame.can_dlc; i++) {
n += snprintf(buf + n, BUF_SIZ - n, "%02x ", frame.data[i]);//数据
}
if (frame.can_id & CAN_RTR_FLAG)//远程帧
n += snprintf(buf + n, BUF_SIZ - n, "remote request");
fprintf(out, "%s\n", buf);//输出到标准输出或者文件
do {
err = fflush(out);//刷新输出缓冲区
if (err == -1 && errno == EPIPE) {
err = -EPIPE;
fclose(out);
out = fopen(optout, "a");
if (!out)
exit (EXIT_FAILURE);
}
} while (err == -EPIPE);
n = 0;
}
}
exit (EXIT_SUCCESS);
}
二、candump使用说明
candump can0/1/2/3
-
-f <FAMILY> 指定协议族 default PF_CAN --family=FAMILY
-
-t <TYPE> 指定socket类型 --type=TYPE
-
-p <PROTO> 指定特殊协议 --protocol=PROTO
-
--filter=id:mask[:id:mask]... 指定id和验收屏蔽,软件滤波,因此可以设置任意个
-
--help 使用说明
-
-o <filename> 将输出重定向到文件<filename>
-
-d 后台运行,保持当前目录不变,将标准输入、标准输出和标准错误重定向到/dev/null;
-
--version 查看版本号
注:使用SIGTERM 和SIGHUP信号可以停止程序接收数据,使程序结束退出,否则会一直循环接收