嵌入式Linux中的系统编程,网络编程的小知识点

正题开始前,先进行一些对于ARM开发的科普,ARM开发是一个统称,是对于A系列,R系列,M系统内核的微控制器的开发。

cotex-A: 用于大量数据处理的领域 6818-A53,I.MX6U阿尔法开发板(Cortex-A7)

cotex-R: 用于实时性特别强的领域---刹车制动

cotex-M: 主要用于工业控制领域(STM32-M系列单片机,GB系列单片机)

ARM Cortex-A核:

        特点:Cortex-A核是ARM架构中的应用处理器核。它通常用于高性能计算和通用操作系统的执行,如Android、Linux等。Cortex-A核具有多级流水线、超标量执行、乱序执行等高级特性,使其能够高效处理多线程和多任务。

        应用场景:Cortex-A核广泛应用于智能手机、平板电脑、服务器、网络设备、物联网网关等需要高性能和通用计算的领域。

ARM Cortex-R核:

        特点:Cortex-R核是ARM架构中的嵌入式实时处理器核。它专注于实时性能和可预测性,适用于处理实时控制任务,如汽车电子、工业控制、嵌入式系统等。Cortex-R核具有低延迟、高吞吐量和硬实时性能。

        应用场景:Cortex-R核广泛用于汽车电子控制器、工业自动化、无线通信基站、嵌入式控制系统等需要实时响应和可靠性的领域

ARM Cortex-M核:

        特点:Cortex-M核是ARM架构中的微控制器处理器核。它专注于低功耗、紧凑尺寸和实时控制,适用于小型嵌入式系统。Cortex-M核通常具有单一周期执行、中断处理能力和低功耗模式。

        应用场景:Cortex-M核广泛应用于微控制器(MCU)、传感器节点、物联网设备、嵌入式传感器、医疗设备等需要低功耗和实时控制的领域。

下面主要是对A系列内核处理器中常用的Linux系统中系统编程和网络编程的细小知识点的总结

一、系统编程

1. 进程

1.1 进程的状态

可以划分为就绪态、执行态、睡眠态/挂起态、暂停态、僵尸态、死亡态

1.2 进程的API接口

1.2.1 创建进程

#include

pid_t fork(void);

1.2.2 获取进程号
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);            ---获取当前进程的进程号
pid_t getppid(void);        ---获取当前进程的父进程的进程号
1.2.3 excel函数族
使用fork创建子进程后,除了直接在代码中写要运行的程序外,还可以
借助execl函数族来完成需要运行的程序
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char arg, ...  / (char  *) NULL */);
int execlp(const char *file, const char arg, ... / (char  *) NULL */);
int execle(const char *path, const char arg, ...  /, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
参数:   
        path --- 要执行的程序的路径
        file --- 要执行的程序的名字
        argv --- 以数组的方式作为形参
        envp --- 用户自定义的变量数组
含义:    
        名称带 l  表示 参数以列表的形式传参
        名称带 v  表示 参数以矢量数组(二级指针)的形式传参
        名字带 p  表示 参数会利用环境变量来寻找指定的文件
        名字带 e  表示 参数会利用用户自定义的环境变量
1.2.4 进程返回值
程序在结束时写的return 0 ,exit(-1)这些都是会交给父进程的。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);        //接收一个子进程的信息,保存到status里面
pid_t waitpid(pid_t pid, int *status, int options);
//指定等待进程号为pid的子进程计数,保存到status里面
//options是指接收的模式


宏定义 WIFEXITED(status)  
returns true if the child terminated normally, that is, by call‐ing exit(3) or _exit(2), or by returning from main().
宏定义 WEXITSTATUS(sta)
获取子进程的返回值

1.2.5 进程结束的缓冲区

exit()的功能和return是一样的

_exit()不会刷新IO缓冲区

#include

int atexit(void (*function)(void));

1.2.6 精灵进程/守护进程

代码注释:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>

int main(int argc, char const *argv[])
{
    // 忽略终端关闭信号
    signal(SIGHUP,SIG_IGN);

    // 创建子进程,父进程退出
    int ret = fork();

    if (ret > 0)
    {
        return 0;
    }
    // 创建心的回话终端
    setsid();
    
    // 创建子进程,父进程退出
    ret = fork();
    if (ret > 0)
    {
        return 0;
    }
    // 创建新的会话组
    setpgrp();

    // 关闭所有文件描述符
    int maxfd = sysconf(_SC_OPEN_MAX);
    int i;
    for (i = 0; i < maxfd; i++)
    {
        close(i);
    }

    // 去掉所有文件权限
    umask(0);

    // 修改工作路径
    chdir("/");

    // 精灵进程需要做的事就放在while循环中
    while (1)
    { }
    return 0;
}

流程如图:

2. 管道和信号

2.1 无名管道的API接口

#include

int pipe(int pipefd[2]);

参数: int类型的具有2个元素的数组,当创建无名管道成功以后,管道的读端和写端会分别放入到数组中去。

成功后:

pipefd[0] 是读端

pipefd[1] 是写端

在linux下一切都是文件,所以管道也可以用read.write操作。

2.2 有名管道的API接口

#include

#include

int mkfifo(const char *pathname, mode_t mode);

参数: pathname 有名管道的文件名

mode 有名管道的权限

成功后:创建管道文件

2.3 文件是否存在以及权限函数

#include

int access(const char *pathname, int mode);

检查某一个文件的权限

pathname: 要被检查的文件

mode: 要被检查的权限

R_OK 读权限

W_OK 写权限

X_OK 执行权限

F_OK 是否存在

成功返回0,失败返回-1

        有名管道和无名管道可以通过 ulimited -a 来查看大小,一般情况下,管道中存储数据的上限是65536个字节,但是也可以通过ulimited指令来修改。

2.4 信号

linux中现有62个信号,编号为 1~31, 34~64 kill -l查看当前版本中linux支持的信号

信号的特点:

        (1)1~31号信号是linux已经有默认的动作,当进程接收到这些信号时,可以执行

默认的动作;34~64号信号没有默认的动作,必须自行设置。

        (2)1~31号信号也被称为不可靠信号,非实时信号。接收n次,可能只会执行1~2次;

34~64号信号称为可靠信号,实时信号。接收n次,一定会执行n次。

        (3)当所有信号一起从挂起状态转入响应状态时,非实时信号不会排队,会相互嵌套

甚至丢包,实时信号会按照从大到小的顺序依次执行。

2.4.1 非实时信号有默认动作
SIGINT    关闭终端--ctrl+C关闭
SIGHUP    关闭terminal终端
SIGKILL   杀死信号
SIGSTOP   暂停进程--让进程进入暂停态
SIGCONT   继续进程--让进程由暂停态进入就绪态
在shell终端中向某一个进程发送信号
kill 信号编号  进程号
在代码中向某一个进程发送信号
#include <signal.h>
int kill(pid_t pid, int sig);
参数:    
        pid就是要发送信号的进程的进程号
        sig就是要发送的信号的编号
2.4.2 信号处理函数
#include <signal.h>
typedef void (*sighandler_t)(int);    
---函数指针:指向一个参数为int,返回值为void的函数的指针,
其中这个参数int,就是接收到的信号的编号
sighandler_t signal(int signum, sighandler_t handler);
参数:  signum: 要响应的信号
handler: 要响应的函数的指针
也可以设置为 SIG_DFL   SIG_IGN

信号处理的三个动作:
    (1)执行linux默认的信号动作,设置 handler为SIG_DFL
    (2)执行用户自己定义的信号动作,设置 handler为自定义函数
    (3)忽略系统发来的信号,设置 handler为 SIG_IGN
2.4.3 信号挂起

        SIGKILL和SIGSTOP信号是不可能被挂起或者忽略的。因为这两个信号涉及到进程的状态改变。

        SIGKILL让进程由执行态转为僵尸态

        SIGSTOP让进程由执行态转为暂停态

        SIGCONT和SIGSTP虽然也会改变进程状态,但仍然可以被忽略或者挂起

例子及代码

有名管道:

fifo_read.c

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <strings.h>

int main(int argc, char const *argv[])
{
    if (access("/home/gec/123",F_OK) != 0)
    {
        // 创建管道文件
        mkfifo("/home/gec/123",0666);
    }
    int fd = open("/home/gec/123",O_RDONLY);
    if (fd == -1)
    {
        perror("open failed!\n");
        return -1;
    }
    
    char buf[32];
    while (1)
    {
        bzero(buf,32);
        read(fd,buf,32);
        printf("buf = %s\n",buf);
    }
    close(fd);
    return 0;
}

fifo_write.c

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    if (access("/home/gec/123",F_OK) != 0)
    {
        // 创建管道文件
        mkfifo("/home/gec/123",0666);
    }
    int fd = open("/home/gec/123",O_WRONLY);
    if (fd == -1)
    {
        perror("open failed!\n");
        return -1;
    }
    
    char buf[32];
    while (1)
    {
        bzero(buf,32);
        scanf("%s",buf);
        write(fd,buf,strlen(buf));
    }
    close(fd);
    
    return 0;
}

3 消息队列

ipcs -q 只查看消息队列的信息

ulimit -q 消息队列的缓冲区大小

3.1 创建键值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:      pathname  路径
proj_id   模数
相同的路径+相同模数一定会得到一样的键值
如果两个进程需要用到同一个消息队列,那么这两个进程创建键值时
使用的路径与模数一定是一模一样的,如果两个进程不需要同一个消息队列,
那么使用路径与模数至少要有一个不同。
返回值:   
        >0   新的合法的键值
        -1   失败

3.2 获取消息队列的ID号

#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数:      key  键值
msgflg  模式
IPC_CREAT  假设键值对应的消息队列不存在,则创建
IPC_EXCL   假设键值对应的消息队列存在,则报错 EEXIST
返回值:   
            >0   消息队列的ID号
            -1   失败

3.3 消息队列的信息传输

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//通过消息队列发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:      msqid   消息队列ID号
msgp    要传输的数据的首地址,它一定是struct msgbuf类型的结构体指针
msgsz   要传输的数据大小
msgflg  模式,一般设置是否阻塞,可以默认写0
//通过消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
参数:      msqid   消息队列ID号
msgp    要接收的数据的缓冲区首地址
msgsz   要传输的数据大小
msgtyp  消息标识
msgflg  模式,一般设置是否阻塞,可以默认写0
消息队列在传输时,一定是以结构体的方式传输。这个结构体内参数的顺序是不能改的。
struct msgbuf 
{
    long mtype;       //标识位,必须>0
    char mtext[1];    //具体传输的数据
};

3.4 控制消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:     
        msqid  消息队列的ID号
        cmd    指令    IPC_STAT ,第三个参数是存在的,表示把消息队列的信息保存到第三个参数的结构体中
        IPC_SET  ,第三个参数存在,表示通过第三个参数设置修改消息队列
        PC_RMID ,第三个参数不存在,为NULL,表示删除消息队列

3.5 在终端中删除消息队列

ipcrm -Q 键值

ipcrm -q ID号

3.6(重要,面试)消息队列的用途

三大作用:

削峰填谷,异步处理,模块解耦

双十二电商购买活动节

(1) 削峰填谷 在活动节期间,流量暴增,平常的服务器负荷不够。每个用户单独处理要500ms,现在突然有 100个用户,那么现在一共需要 50000 才能处理完毕。可以把这100个用户拆分,拆分到10个消息队列 中,那么每个消息队列最多只需要处理5000ms的内容,当人数减少以后,可以通过销毁消息队列来减少 服务器负荷。

(2) 异步处理 在活动节期间,每个用户的购买流程有主要流程和次要流程。例如 购买就是主要流程,要400ms, 领消费券,做打折活动是次要流程,分别要50ms。这次次要流程可以放入到另一个消息队列里面,那么 主要流程的响应时间就只有400ms了。

(3) 模块解耦 在活动期间,需要一个数据库来分析保存用户的购买习惯,这些保存和分析数据也是需要时间的, 如果这些也被放入到购买流程中,那么会影响到用户体验,并且不同的活动期间,你的数据库分析算法 也有修改,直接把这个代码加入到购买流程的代码,会加大程序员的工作量。解决办法就是把算法分析 的进程与购买流程的进程完全分离,两个是完全不同的进程。通过消息队列把购买时的数据发送给算法 分析的进程。

例子及代码

消息队列接收端

msgrcv

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
 
struct msgbuf{
    long mtype;
    char mtext[100];
};

 int main()
{
    	//创建键值
    	key_t key = ftok(".",1);
    	if(key == -1)
    	{
    		perror("ftok failed");
    		return -1;
    	}
    	
    	//创建或者打开消息队列的ID号
    	int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0666);
    	if(msgid == -1)
    	{
    		//只有当消息队列是因为已存在而报错的情况下,才能直接打开
    		if( errno == EEXIST)
    		{
    			msgid = msgget(key,0666);
    			if(msgid == -1)
    			{
    				perror("msgget failed");
    				return -1;
    			}
    		}
    	}
    	
    	struct msgbuf buf;
    	while(1)
    	{
    		//接收标志为2的消息
    		msgrcv(msgid,&buf,sizeof(struct msgbuf), 2 ,0);
    		printf("recv:[%s]\n",buf.mtext);
    		
    		if(strcmp(buf.mtext,"88")==0)
    			break;
    	}

	
}

发送端

msgsnd

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
 
//结构体中成员的顺序绝对不能改,因为内核在处理消息队列时是先读4字节,再读n-4字节
struct msgbuf{
    long mtype;
    char mtext[100];
};

int main()
{
    	//创建键值
    key_t key = ftok(".",1);
    	if(key == -1)
    	{
    		perror("ftok failed");
    		return -1;
    	}
    	
    	//创建或者打开消息队列的ID号
    	int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0666);
    	if(msgid == -1)
    	{
    		//只有当消息队列是因为已存在而报错的情况下,才能直接打开
    		if( errno == EEXIST)
    		{
    			msgid = msgget(key,0666);
    			if(msgid == -1)
    			{
    				perror("msgget failed");
    				return -1;
    			}
    		}
    	}
    	
    	//发送数据时是向消息队列中写入标识符为2的信息
    	struct msgbuf buf;
    	buf.mtype = 2;
    	while(1)
    	{
    		scanf("%s",buf.mtext);
    		msgsnd(msgid,&buf,sizeof(struct msgbuf),0);
    		
    		if(strcmp(buf.mtext,"88")==0)
    			break;
    	}
    	
    	//删除消息队列
    	//msgctl(msgid,IPC_RMID,NULL);
	
}

系统编程知识点还有很多,篇幅有限,不再展现,在最后会有我的笔记相关链接

下面接收一波网络编程

二 、网络编程

        网络编程也是属于进程之间的通信方式,和系统编程中的其他几种通信方式的区别:

系统编程:作用于同一台主机下的两个不同进程之间的通信,使用管道、IPC对象

网络编程:作用域同一台主机或跨主机之间的通信,使用局域网进行通信

2.1 网络编程的专业术语

2.1.1 网络体系结构模型

OSI模型(7层)

OSI模型在发送的过程中,数据从引用层出发,每经过一层就会在数据后面加上这一层的"头",除了最后的物理层

OSI模型在接收的过程中,数据从物理层传入,每经过一层就会去掉对应层的"头",最后只剩数据来到应用层

除了OSI模型,还有TCP/IP模型(4层)

        应用层 老板将自己要说的话写成信。封装好后在信封上附上对方的地址

        传输层 快递员拿到信决定以什么方式去送

        网络层 分析地址在哪

        网络接口与物理层 选择一条正确的路就出发

2.1.2 什么是IP地址

例如:"192.168.33.9"点分制

        在同一个局域网下,每一台主机都必须要有一个IP地址,且IP地必须是相同网段:IP地址的前3个数字

        所以只能通过最后一个数字来划分给同一局域网下的不同主机

        每一个IP地址都是32位,如果要在网络编程中使用IP地址,就要将这32位的数据转换成网络字节序

        我们在写代码时写的是主机字节序,要将IP地址转换成网络字节序后再将数据发出

        会使用一些字节转换函数

2.1.3 什么是端口号

        在同一台主机下的每一个进程都共用一个IP地址,如何区分同一IP地址下的不同进程?

        通过端口号,每一个进程都会有被分配一个端口号用来接收数据

        端口号是16位,取值范围:0 ~ 65535

        系统默认占用端口:0 ~ 1023

        用户可以自定义的:1024 ~ 65535

        端口号在结束使用后会被回收,所以最好过一段时间再用

2.1.4 ubuntu网络命令

要让ubuntu和主机处于同一局域网下,就要去配置主机IP地址

查看/配置linux网络信息:ifconfig

gec@ubuntu:/mnt/hgfs/2210/05网络编程/day1$ ifconfig

ens33(网卡名)            Link encap:Ethernet 以太网

                                   HWaddr 00:0c:29:4b:3d:ee 硬件地址

                                   inet addr:192.168.33.5 当前ubuntu的IP地址

                                   Bcast:192.168.33.255 当前局域网的广播地址

                                   Mask:255.255.255.0 子网掩码

设置ubuntuIP地址

        sudo ifconfig 网卡名称 IP地址

        注意:网卡名称要看清楚,不同的linux,它的网卡名也会有不同,还有IP地址最好的同一网段下的

        例如:

                sudo ifconfig ens33 192.168.33.6

                然后可以再使用ifconfig查看一下是否配置成功了

检查是否可以与同一局域网下的其他主机进行通信

        ping 其他主机的IP地址

        例如:

                gec@ubuntu:/mnt/hgfs/2210/05网络编程/day1$ ping 192.168.33.9

                PING 192.168.33.9 (192.168.33.9) 56(84) bytes of data.

                64 bytes from 192.168.33.9: icmp_seq=1 ttl=128 time=0.160 ms

                64 bytes from 192.168.33.9: icmp_seq=2 ttl=128 time=0.557 ms

                出现"time=0.557 ms"就说明可以进行通信,有一点延迟

如果ping不通,检查一下电脑防火墙关了没

2.1 通信协议

        在不同计算机中,双方需要遵循同一个规则的通信协议来进行通信

        TCP/IP协议 传输控制协议/以太网互联协议

        TCP协议 用于检查传输过程中出现的问题

        IP协议 负责不同网络之间的数据交换

        传输的过程中,一旦发现错误,要求重新传输数据,直到数据安全为止

2.1.1 TCP协议(打电话)

        特点:字节传输、面向连接、效率相对较低

        优点:提供可靠传输,确保数据无丢失

        缺点:点对点,一次只能和一个对象进行通信

        应用场合:重要信息的传输,长时间的传输

2.1.2 UDP协议(写信)

        特点:数据包传输、面向无连接、效率相对较高

        优点:可以实现点对多的通信

        缺点:传输时可能会发生数据包丢失

        应用场合:网络媒体的视频、直播

2.1.3 TCP协议和UDP协议的区别

UDP:数据包式套接字

        一种对网络要求不高的传输方式,它的传输数据不会受到接收端的影响

        UDP不可靠传输,只管发送,不负责对方是否接收成功

TCP:数据流式套接字

        一种对网络要求较高的传输方式,每次传输数据过去,都必须接收到对方的响应

        在连接时,会有一个"三次握手"的操作

        在断开时,会有一个"四次挥手"的操作

        TCP的可靠传输就是建立在这个三次握手和四次挥手的操作上,需要连接成功才能传输/接收数据

2.1.4 三次握手和四次挥手

专用术语:

        SYN: 同步位,表示请求连接,一般为1

        seq: 序号,一般是随机数

        ACK: 确认位,ACK=1表示有效,无值表示无效

        ack: 确认号,在对方发送的序号基础上+1

        FIN: 取消位,表示请求断开,一般为1

三次握手:

四次挥手:

TCP通信是一种面向连接的通信方式,要确保和对方建立了连接后才能开始通信

而且在通信结束时也会和对方进行断开连接的动作,建立连接时的动作就是三次握手,断开连接时的操作就是四次挥手

三次握手和四次挥手参考链接

三、参考笔记

由于篇幅有限,系统编程和网络编程知识点也比较多,具体可以参考我的笔记

系统编程笔记

网络编程笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值