Linux 底软开发——对CAN的详细操作(周期发送,异常检测,过滤报文)

news2024/11/19 7:23:04

Linux底软开发—对CAN发送接收详细操作

文章目录

  • Linux底软开发—对CAN发送接收详细操作
    • 1.保证多条CAN数据发送的周期性
    • 2.解析CAN报文数据
    • 3.CAN总线异常机制应对
    • 4.对CAN报文进行过滤操作
    • 5.完整的接收报文代码(过滤,心跳检测,解析)

1.保证多条CAN数据发送的周期性

如果想同时发送多条CAN,在Linux下可以使用多线程操作,一条线程对应一条CAN报文

 // 启动线程发送CAN消息
    for (int i = 0; i < 3; ++i)
    {
        pthread_create(&tid[i], NULL, send_data, (void *)&data_args[i]);
    }

    // 等待所有线程结束
    for (int i = 0; i < 3; ++i)
    {
        pthread_join(tid[i], NULL);
    }

保证每条CAN报文的周期性,可以使用Linux的时间函数—usleep() ,精确到微秒,同样每个线程可以接受时间间隔参数

示例代码:循环发送多条CAN报文,自定义时间周期,每一条CAN报文都可以自定义自己的周期

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <pthread.h>

#define CAN_INTERFACE "can0"
#define CAN_ID_1 0x123
#define CAN_ID_2 0x124
#define CAN_ID_3 0x125
#define CAN_DATA_LEN 8

struct send_data_args
{
    int sockfd;
    struct can_frame frame;
    int64_t interval; // 发送周期
};

void send_can_message(int sockfd, struct can_frame *frame)
{
    int nbytes;
    nbytes = write(sockfd, frame, sizeof(struct can_frame));
    if (nbytes < 0)
    {
        perror("Write failed");
        exit(EXIT_FAILURE);
    }
}

void *send_data(void *args)
{
    struct send_data_args *data_args = (struct send_data_args *)args;
    while (1)
    {
        send_can_message(data_args->sockfd, &(data_args->frame));
        printf("Sent CAN message with ID 0x%X\n", data_args->frame.can_id);
        usleep(data_args->interval);
    }
    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    struct sockaddr_can addr;
    struct ifreq ifr;
    int sockfd;
    int64_t time_stval; // 时间间隔周期
    if (argc < 3)
    {
        // 如果未提供时间周期参数,则默认为100毫秒
        time_stval = 100 * 1000;
    }
    else
    {
        char *str = argv[2]; // 一个数字字符串
        int64_t result;
        // 使用 strtoll 函数将 char* 转换为 int64_t
        char *endptr;
        result = strtoll(str, &endptr, 10);

        // 检查转换是否成功
        if (*endptr != '\0')
        {
            printf("Conversion failed. Not a valid number.\n");
            return 1;
        }
        time_stval = result;
    }

    printf("调试信息如下:\n");
    printf("接收参数个数:%d\n", argc);
    printf("can接口为:%s\n", argv[1]);
    printf("时间间隔周期为:%ld\n", time_stval);

    pthread_t tid[3];
    struct send_data_args data_args[3];

    // 创建socket
    if ((sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
    {
        perror("Socket creation failed");
        return EXIT_FAILURE;
    }

    // 设置CAN接口 从外部输入指定
    strcpy(ifr.ifr_name, argv[1]);
    ioctl(sockfd, SIOCGIFINDEX, &ifr);

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

    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("Binding failed");
        close(sockfd);
        return EXIT_FAILURE;
    }

    // 准备CAN消息
    data_args[0].sockfd = sockfd;
    data_args[0].frame.can_id = CAN_ID_1;
    data_args[0].frame.can_dlc = CAN_DATA_LEN;
    memset(data_args[0].frame.data, 0, CAN_DATA_LEN); // 清空数据
    data_args[0].interval = 200 * 1000; // 第一个CAN报文的发送周期为200毫秒

    data_args[1].sockfd = sockfd;
    data_args[1].frame.can_id = CAN_ID_2;
    data_args[1].frame.can_dlc = CAN_DATA_LEN;
    memset(data_args[1].frame.data, 0, CAN_DATA_LEN); // 清空数据
    data_args[1].interval = 300 * 1000; // 第二个CAN报文的发送周期为300毫秒

    data_args[2].sockfd = sockfd;
    data_args[2].frame.can_id = CAN_ID_3;
    data_args[2].frame.can_dlc = CAN_DATA_LEN;
    memset(data_args[2].frame.data, 0, CAN_DATA_LEN); // 清空数据
    data_args[2].interval = 400 * 1000; // 第三个CAN报文的发送周期为400毫秒

    // 启动线程发送CAN消息
    for (int i = 0; i < 3; ++i)
    {
        pthread_create(&tid[i], NULL, send_data, (void *)&data_args[i]);
    }

    // 等待所有线程结束
    for (int i = 0; i < 3; ++i)
    {
        pthread_join(tid[i], NULL);
    }

    close(sockfd);
    return EXIT_SUCCESS;
}

测试结果

在这里插入图片描述

上图中定义0x123 每隔200ms 发送数据,时间误差为0.0001s,

2.解析CAN报文数据

CAN报文的数据格式通常由CAN帧的数据域(Data Field)和数据长度码(Data Length Code,DLC)组成。具体格式如下:

  1. CAN标识符(CAN Identifier):用于标识CAN消息的ID。在标准CAN帧中,ID为11位;在扩展CAN帧中,ID为29位。ID可以表示消息的优先级、消息类型等信息。
  2. 远程传输请求位(Remote Transmission Request,RTR):用于标识消息是数据帧还是远程帧。数据帧包含实际的数据,而远程帧则不包含数据,仅用于请求数据。RTR位为0表示数据帧,为1表示远程帧。
  3. 数据长度码(Data Length Code,DLC):指示了CAN帧数据域中包含的数据字节数。DLC的取值范围通常为0到8。
  4. 数据域(Data Field):包含了CAN消息的实际数据。数据域的大小由DLC决定,最大为8个字节。
  5. CRC校验码(Cyclic Redundancy Check,CRC):用于检测CAN帧在传输过程中的错误。CRC通常由CAN控制器自动生成和验证。
  6. 确认位(ACK):用于确认CAN消息是否被成功接收。CAN总线上的所有节点都可以接收CAN消息,并通过ACK位来确认消息是否被正确接收。
  7. 结束位(End of Frame,EOF):指示了CAN帧的结束。
--------------------------------------------------------------------
| Bit Position |  0-1  |  2-12  |  13   |  14-17  |  18-25  |  26-31 |
|--------------|-------|--------|-------|---------|---------|--------|
|      Field   |  SOF  |   ID   |   RTR |   DLC   |  Data   |   CRC  |
--------------------------------------------------------------------

在LinuxC编程中,接收CAN报文,系统API 已经封装好了结构体,对于开发者来讲,取到数据之后,读取can_frame结构体即可

结构体定义如下:

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)));
};

读取示例:

  printf("Received CAN frame:\n");
        printf("ID: %03X\n", frame.can_id);
        printf("Length: %d\n", frame.can_dlc);
        printf("Data: ");
        for (int i = 0; i < frame.can_dlc; i++)
        {
            printf("%02X ", frame.data[i]);
        }
        printf("\n");

具体功能需要解析frame.data 数据,根据通信矩阵,十六进制字节数据转为二进制 逐一分析即可

3.CAN总线异常机制应对

解决方案:引入心跳机制,即固定发送时间间隔的固定CAN_ID ,每一次得到CAN数据之后,就更新最后一次获取时间,引入心跳检测线程,一直判断是否超时,如果超时,说明CAN发送异常,数据未及时发送,或者掉线等其他原因,超时之后,再根据业务进行捕获异常。

示例代码: 在接受线程中,检测ID是否为心跳包,如果是心跳包,更新最后一次接收时间

// 检查是否为心跳包
        if (frame.can_id == HEARTBEAT_ID)
        {
            // 更新心跳包接收时间
            gettimeofday(&last_heartbeat_time, NULL);
        }

注意:需要将last_heartbeat_time设置为全局变量,方便检测线程获取最新的时间

// 心跳包检测线程函数
void *heartbeat_checker(void *arg)
{
    struct timeval current_time;
    double elapsed_time;
    while (1)
    {
        // 获取当前时间
        gettimeofday(&current_time, NULL);
        // 计算与上次心跳包的时间间隔
        elapsed_time = difftime(current_time.tv_sec, last_heartbeat_time.tv_sec);
        // 检查是否超时
        if (elapsed_time > TIMEOUT_SEC)
        {
            online = -1;
            printf("CAN node is offline.\n");

            // 清空接收缓冲区 实现逻辑
            // ioctl(can_socket, SIOCINQ, 0);
        }
        // 休眠1秒
        sleep(1);
    }
    return NULL;
}

4.对CAN报文进行过滤操作

可以设置过滤表,将需要的ID放入过滤表中,并且设置当前状态是否可用

例如在运行目录读取过滤表设置文件

filter_table.ini 文件内容

#ID STATUS 录入数据请用空格隔开
0x123 enable
0x122 enable
0x111 disable

在程序初始化之后,读取过滤表,一定要在读取CAN报文线程之前运行。判断哪些可以接收并放入数组中,为下一步过滤做判断

代码实现:

//得到过滤表中可用的ID
void getEnableIds()
{
    FILE *file;
    char line[MAX_LINE_LENGTH];
    FilterEntry entries[MAX_IDS];
    int count = 0;
    int ptr = 0;
    // 打开过滤表文件
    file = fopen("filter_table.ini", "r");
    if (file == NULL)
    {
        printf("filter_table.ini");
        perror("文件不存在,请检查路径\n");
        return 1;
    }
    // 逐行读取文件内容
    while (fgets(line, sizeof(line), file) != NULL)
    {
        // 跳过以 "#" 开头的注释行
        if (line[0] == '#')
        {
            continue;
        }

        // 解析每行,提取CAN信号ID和状态
        unsigned int id;
        char status[10];
        if (sscanf(line, "%x %s", &id, status) == 2)
        {
            // 将CAN信号ID和状态存储到数组中
            entries[count].id = id;
            entries[count].enabled = (strcmp(status, "enable") == 0) ? 1 : 0;
            if (strcmp(status, "enable") == 0)
            {
                // 可用ID
                enable_id[ptr] = id;
                ptr++;
                enable_count++;
            }
            count++;
            if (count >= MAX_IDS)
            {
                printf("Maximum number of entries reached. Aborting.\n");
                break;
            }
        }
    }
    // 关闭文件
    fclose(file);
    printf("Read enable %d filter table entries:\n", ptr);
    for (int i = 0; i < ptr; i++)
    {
        printf("Entry %d: ID = 0x%X\n", i + 1, enable_id[i]);
    }
}

在读取过程中,判断报文是否可用,是否存在可用数组中。

 // 判断是否是可用的ID 不是可用的直接pass
        if(isIdExists(frame.can_id,enable_id,enable_count)==-1) continue;
        // 打印接收到的CAN帧数据
        printf("Received CAN frame:\n");
        printf("ID: %03X\n", frame.can_id);
        printf("Length: %d\n", frame.can_dlc);
        printf("Data: ");
        for (int i = 0; i < frame.can_dlc; i++)
        {
            printf("%02X ", frame.data[i]);
        }
        printf("\n");

5.完整的接收报文代码(过滤,心跳检测,解析)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <sys/time.h>

#define CAN_INTERFACE "can0" // 替换为你的CAN接口名称
#define HEARTBEAT_ID 0x166   // 心跳包的CAN消息ID
#define TIMEOUT_SEC 3        // 等待时长

struct timeval last_heartbeat_time; // 设为全局变量
int can_socket;
int online = 1;
// 过滤表相关 定义
#define MAX_IDS 100         // 最大支持的CAN信号ID数量
#define MAX_LINE_LENGTH 100 // 最大行长度
typedef struct
{
    unsigned int id;
    int enabled; // 0为disable,1为enable
} FilterEntry;

unsigned int enable_id[MAX_IDS];
int enable_count=0;

//得到过滤表中可用的ID
void getEnableIds()
{
    FILE *file;
    char line[MAX_LINE_LENGTH];
    FilterEntry entries[MAX_IDS];
    int count = 0;
    int ptr = 0;
    // 打开过滤表文件
    file = fopen("filter_table.ini", "r");
    if (file == NULL)
    {
        printf("filter_table.ini");
        perror("文件不存在,请检查路径\n");
        return 1;
    }
    // 逐行读取文件内容
    while (fgets(line, sizeof(line), file) != NULL)
    {
        // 跳过以 "#" 开头的注释行
        if (line[0] == '#')
        {
            continue;
        }

        // 解析每行,提取CAN信号ID和状态
        unsigned int id;
        char status[10];
        if (sscanf(line, "%x %s", &id, status) == 2)
        {
            // 将CAN信号ID和状态存储到数组中
            entries[count].id = id;
            entries[count].enabled = (strcmp(status, "enable") == 0) ? 1 : 0;
            if (strcmp(status, "enable") == 0)
            {
                // 可用ID
                enable_id[ptr] = id;
                ptr++;
                enable_count++;
            }
            count++;
            if (count >= MAX_IDS)
            {
                printf("Maximum number of entries reached. Aborting.\n");
                break;
            }
        }
    }
    // 关闭文件
    fclose(file);
    printf("Read enable %d filter table entries:\n", ptr);
    for (int i = 0; i < ptr; i++)
    {
        printf("Entry %d: ID = 0x%X\n", i + 1, enable_id[i]);
    }
}

// 判断十六进制数值是否存在于数组中的函数
int isIdExists(unsigned int id, unsigned int *enable_id, int size) {
    for (int i = 0; i < size; i++) {
        if (enable_id[i] == id) {
            return 1; // 存在
        }
    }
    return -1; // 不存在
}

// 心跳包检测线程函数
void *heartbeat_checker(void *arg)
{
    struct timeval current_time;
    double elapsed_time;
    while (1)
    {
        // 获取当前时间
        gettimeofday(&current_time, NULL);
        // 计算与上次心跳包的时间间隔
        elapsed_time = difftime(current_time.tv_sec, last_heartbeat_time.tv_sec);
        // 检查是否超时
        if (elapsed_time > TIMEOUT_SEC)
        {
            online = -1;
            printf("CAN node is offline.\n");

            // 清空接收缓冲区 实现逻辑
            // ioctl(can_socket, SIOCINQ, 0);
        }
        // 休眠1秒
        sleep(1);
    }
    return NULL;
}

// CAN数据接收线程函数
void *can_receiver(void *arg)
{
    struct can_frame frame;
    int nbytes;
    while (1)
    {
        nbytes = read(can_socket, &frame, sizeof(struct can_frame));
        if (nbytes < 0)
        {
            perror("read");
            break;
        }
        else if (nbytes < sizeof(struct can_frame))
        {
            fprintf(stderr, "read: incomplete CAN frame\n");
            break;
        }
        // 检查是否为心跳包
        if (frame.can_id == HEARTBEAT_ID)
        {
            // 更新心跳包接收时间
            gettimeofday(&last_heartbeat_time, NULL);
        }
        // 判断是否是可用的ID 不是可用的直接pass
        if(isIdExists(frame.can_id,enable_id,enable_count)==-1) continue;
        // 打印接收到的CAN帧数据
        printf("Received CAN frame:\n");
        printf("ID: %03X\n", frame.can_id);
        printf("Length: %d\n", frame.can_dlc);
        printf("Data: ");
        for (int i = 0; i < frame.can_dlc; i++)
        {
            printf("%02X ", frame.data[i]);
        }
        printf("\n");
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    int s;
    struct sockaddr_can addr;
    struct ifreq ifr;
    pthread_t checker_thread;
    pthread_t receiver_thread;
    getEnableIds();
    printf("正在等待%s的数据.....\n", argv[1]);
    // 创建socket
    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (s == -1)
    {
        perror("socket");
        return 1;
    }
    can_socket = s; // 将socket赋给全局变量

    // 绑定CAN接口
    strcpy(ifr.ifr_name, argv[1]);
    ioctl(s, SIOCGIFINDEX, &ifr);
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        perror("bind");
        close(s);
        return 1;
    }

    // 创建心跳包检测线程
    if (pthread_create(&checker_thread, NULL, heartbeat_checker, NULL) != 0)
    {
        perror("pthread_create");
        close(s);
        return 1;
    }

    // 创建CAN数据接收线程
    if (pthread_create(&receiver_thread, NULL, can_receiver, NULL) != 0)
    {
        perror("pthread_create");
        close(s);
        return 1;
    }

    // 等待线程结束
    pthread_join(checker_thread, NULL);
    pthread_join(receiver_thread, NULL);

    // 关闭socket
    close(s);

    return 0;
}

测试效果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1620863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

刚刚,百度真来炸场了!

文章目录 前言再看文心智能体开发工具&#xff1a;AgentBuilderAI原生应用开发工具&#xff1a;AppBuilder各种尺寸的模型定制工具&#xff1a;ModelBuilder结语 前言 不知道大家还记得不&#xff0c;去年 ChatGPT 火遍全球的时候&#xff0c;国内率先推出 AI 大模型的是百度家…

【热门话题】AI作画算法原理解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 AI作画算法原理解析AI作画算法概述基础原理&#xff1a;机器学习与深度学习卷积…

再谈C语言——理解指针(四)

assert断⾔ assert.h 头⽂件定义了宏 assert() &#xff0c;⽤于在运⾏时确保程序符合指定条件&#xff0c;如果不符合&#xff0c;就报错终⽌运⾏。这个宏常常被称为“断⾔”。 assert(p ! NULL); 上⾯代码在程序运⾏到这⼀⾏语句时&#xff0c;验证变量 p 是否等于 NULL 。…

基于Kepware的Hadoop大数据应用构建-提升数据价值利用效能

背景 Hadoop是一个由Apache基金会所开发的分布式系统基础架构&#xff0c;它允许用户在不需要深入了解分布式底层细节的情况下&#xff0c;开发分布式程序。Hadoop充分利用集群的威力进行高速运算和存储&#xff0c;特别适用于处理超大数据集。 Hadoop的生态系统非常丰富&…

【如何使用chrome开发者工具调试javascript代码】

创作背景 平常开发时我们去解决bug可能会用console.log来来调试&#xff0c;但是断点可以更快地完成工作。 log调试的劣势&#xff1a;但是使用console.log时需要我们手动的打开源码&#xff0c;找到相关代码&#xff0c;插入log语句&#xff0c;然后重新加载页面&#xff0c…

【MySQL】数据库操作指南:数据类型篇

&#x1f331;博客主页&#xff1a;青竹雾色间 &#x1f331;系列专栏&#xff1a;MySQL探险日记 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 文章目录 1. 数值类型1.1 tinyint 类型1.2 bit 类型1.3 小数类型1.3.1 f…

屏幕状态自动检测+鼠标自动操作

目录 一、写在前面 1.1适用场景 1.2涉及到的库 二、函数库 2.1pyautogui-屏幕截图&鼠标操作 2.1.1屏幕截图screenshot函数 2.1.2鼠标移动及单击 2.2Opencv-模板匹配 2.2.1matchTemplate函数 2.2.2minMaxLoc函数 2.2.3相关代码 2.3base64-图片转base64 2.3.1在线…

顺序表的应用-通讯录

顺序表的应用-通讯录 1.操作2.功能要求2.1.功能要求2.2.思路小结2.3.文件梳理2.4.代码实现"SeqList.h""Contact.h""SeqList.c""Contact.c""test.c" 1.操作 链接: 顺序表专题 这篇文章介绍了顺序表的概念与基本操作。 本文将…

54位大咖演讲精华! 中国生成式AI大会圆满收官,TOP50企业榜单揭晓

54位大咖演讲精华&#xff01; 中国生成式AI大会圆满收官&#xff0c;TOP50企业榜单揭晓© 由 红板报 提供 智东西4月19日报道&#xff0c;为期两天的2024中国生成式AI大会&#xff0c;今日在京圆满收官。 54位产学研投嘉宾代表全程干货爆棚&#xff0c;报名咨询人数逾52…

机械臂模型更换成自己的urdf模块

1.将urdf生成slx文件 smimport(rm_65_flange.urdf);%生成Simscape物理模型 2.更换joint部分&#xff08;对应与几个输入几个输出&#xff09;&#xff08;依次更换&#xff09; 3.更改关节部分&#xff08;依次更换&#xff09; 找到urdf文件夹下的meshes文件夹&#xff0c;看…

python爬虫 - 爬取 json 格式数据(股票行情信息:雪球网,自选股)

文章目录 1. 第一步&#xff1a;安装requests库2. 第二步&#xff1a;获取爬虫所需的header和cookie3. 第三步&#xff1a;获取网页4. 第四步&#xff1a;解析网页5. 第五步&#xff1a;解析 json 结构数据体6. 代码实例以及结果展示 python爬虫五部曲&#xff1a; 第一步&…

Window + Ubuntu 双系统无Ubuntu Bios 启动项

文章目录 安装硬盘位置不重要&#xff01;&#xff01;&#xff01;&#xff08;但是我安装在了第二张HDD&#xff09;问题是多盘分位置会导致磁盘主分区变成了简单卷 Bios Ubuntu 启动项修复参考Ubuntu安装U盘进入Try Ubuntu 使用Terminal修复完提示Disable Secure Boot进入Te…

sublime运行编译C和Java

1.先安装终端 参照以下教程 如何在 Sublime 文本编辑器中使用终端&#xff1f;_sublime终端窗口怎么打开-CSDN博客 可能遇到的问题&#xff1a;有些sublime text3可能并没有显示“package control”。这个问题对于笔者来说是有些吊诡的&#xff0c;因为之前一开始安装时是能…

echarts 堆叠柱状图 顶部添加合计

堆叠有3个&#xff0c;后面加了一个对象显示顶部的数据&#xff0c; 其实主要的代码还是在series 的第四项&#xff0c;需要注意的是 series的第四项中的data需要为 data: [0, 0, 0] 顶部的统计才能显示出来 增加的代码如下 {name: 综合,type: bar,stack: total,label: {sh…

tcp inflight 守恒算法的几何解释

接上文&#xff1a;tcp inflight 守恒算法背后的哲学 在 tcp inflight 守恒算法正确性 中&#xff0c;E bw / srtt 的公平最优解是算出来的&#xff0c;如果自然可以用数学描述&#xff0c;那能算出来的东西反过来也一定能通过直感看出来&#xff0c;我倾向于用几何和力学描述…

Linux 深入理解Linux文件系统与日志分析

在Linux系统中&#xff0c;文件名和文件数据是分开存储的 文件数据包含 元信息(即不包含文件名的文件属性) 和 实际数据 文件元信息存储在 inode(索引节点)里&#xff0c; 文件实际数据存储在 block(块)里; 文件名存储在目录块里 查看文件的元信息 stat 文件名 [ro…

Maven基础篇7

私服-idea访问私服与组件上传 公司团队开发流程 本地上传–>repository–>私服 其他成员从私服拿 1.项目完成后发布到私服 在pom文件最后写上发布的配置管理 ​ //写发布的url也就是你发布到哪一个版本&#xff0c;以及写入id ​ ​ 发布的时候&#xff0c;将项…

ubuntu20 中设置桌面背景任务

1. 下载conky 使用 Conky 在 Ubuntu 中显示信息&#xff0c;例如你的阅读计划&#xff0c;可以分几个步骤来完成。Conky 是一款灵活的轻量级系统监视器&#xff0c;能够在桌面上显示各种信息。以下是基本的设置步骤&#xff1a; 安装 Conky 首先&#xff0c;你需要在 Ubuntu…

【Linux学习】Linux进程(二)

文章目录 &#x1f4d5;查看进程&#x1f680;/proc目录&#x1f680;cwd与exe &#x1f4d5;改变进程的工作目录&#x1f680;chdir指令 &#x1f4d5;vim卡住了怎么解决 本篇文章接着【LInux进程&#xff08;一&#xff09;】继续编写。 &#x1f4d5;查看进程 &#x1f68…

java使用trim方法和replaceAll方法去除空格之后,还存在空格

今天使用其他人的一个功能&#xff0c;发现生成的映射少了一个&#xff0c;后面去代码里面debug发现是字符串中左边空格没有去除导致。查看代码&#xff0c;里面是使用了字符串.trim().replaceAll(" ", "")去除空格的。这个代码虽然能去除&#xff08;半角…