系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
TODO:写完再整理
文章目录
- 系列文章目录
- 前言
- 一、linux开发的方向
- 二、Linux环境特点
- 介绍
- Linux环境基本构成
- 三、进程与线程
- 1、进程的概念
- 2、进程的状态
- 3、线程的概念
- 4、线程的概念以及与进程的区别
- 四、同步与互斥
- 1、同步和互斥概念
- 2、同步互斥的方法
- 互斥锁
- 自旋锁
- 读写锁
- 信号量
- 五、共享与通信
- 1、多种进程间通信的方法介绍
- 共享内存
- 消息队列
- 信号量
- 管道(pipe)
- 套接字(socket)
- 总结
前言
认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长!本文先对Linux应用层开发–多线程进程编程做个简单的介绍,具体内容后续再更,其他模块可以参考去我其他文章
提示:以下是本篇文章正文内容
一、linux开发的方向
1、linux系统的使用,如用于测试跑工具等等
2、linux应用层开发,如多线程进程编程,应用层开发应该包括追踪崩溃的发生点等等
3、linux底层开发,如字符开发,设备树开发,网络开发,涉及具体的硬件(整点原子的cpu开发可以入门)
.
.
二、Linux环境特点
介绍
1、开源性:Linux系统及其工具都是开源的,因此,Linux环境编程可以使用大量的免费工具和库。
2、多用户支持:Linux系统是支持多用户和多任务的,这意味着Linux环境编程可以同时处理多个任务和用户。
3、灵活的文件系统:Linux文件系统是非常灵活和可扩展的,这使得Linux环境编程更方便。
4、丰富的命令行工具:Linux系统支持丰富的命令行工具,这使得Linux环境编程更加方便和高效。
5、静态库和动态库:Linux环境编程可以使用静态库和动态库,这使得程序在编译时或运行时可以使用已经编译好的库功能。
6、库和工具的庞大数量:Linux系统中有大量的库和工具,这使得Linux环境编程更加丰富多彩,并且具有广泛的应用前景。
7、POSIX兼容性:Linux系统与POSIX (Portable Operating System Interface)标准兼容,因此,Linux环境编程还可以向其他POSIX兼容系统移植。
.
Linux环境基本构成
Linux系统一般有4个主要部分:内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。
一.Linux内核 内核是操作系统的核心,具有很多最基本功能,如虚拟内存、多任务、共享库、可执行程序和TCP/IP网络功能。Linux内核的模块分为以下几个部分:存储管理、CPU和进程管理、文件系统、设备管理和驱动、网络通信、系统的初始化和系统调用等。
二.Linux根文件系统 Linux内核启动完成之后会mount根文件系统,根文件系统包含系统运行的第一个应用程序(/sbin/init)以及系统基本的工具和脚本。
三.Linux shell shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行,是一个命令解释器。另外,shell编程语言具有普通编程语言的很多特点,用这种编程语言编写的shell程序与其他应用程序具有同样的效果。
四.Linux应用程序 标准的Linux系统一般都有一套都有称为应用程序的程序集,它包括文本编辑器、编程语言、XWindow、办公套件、Internet工具和数据库等。
.
三、进程与线程
1、进程的概念
**狭义定义:**进程就是一段程序的执行过程(process)。
**广义定义:**进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元。
简单来讲进程的概念主要有两点:
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和栈区(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;栈区域存储着活动过程中调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
.
.
2、进程的状态
Linux进程运行状态机
进程的状态
R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。
D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,为了保护进程数据与硬件一致,系统不允许被其他进程中断打断。
Z 是 Zombie 的缩写,它表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。
S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。
I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。前面说了,硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。
T 或者 t,也就是 Stopped 或 Traced 的缩写,表示进程处于暂停或者跟踪状态。向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped)。而当用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是一种特殊的暂停状态,只不过你可以用调试器来跟踪并按需要控制进程的运行。
X是 Dead 的缩写,表示进程已经消亡,所以你不会在 top 或者 ps 命令中看到它。
3、线程的概念
Linux系统中线程是一种轻量级进程,它是进程中的一个控制流程,可以与同一进程中的其他线程共享进程空间、资源和上下文,包括文件描述符、内存、打开的文件和信号处理程序等。与进程不同,线程不拥有自己的地址空间,它们直接访问进程的地址空间。
在Linux系统中,线程的主要特点包括以下几个方面:
1、轻量级:相比进程而言,线程的创建和切换开销较小,具有很好的响应性能和实时性能,适用于多任务系统。
2、共享资源:线程与进程共享进程的资源,包括内存、文件句柄、信号处理器等。
3、并发执行:多个线程可以同时执行,具有较高的并发性。
4、内存共享:线程共享进程的地址空间,可以通过共享内存来实现互相通信或共享数据。
5、线程安全:在多线程环境下,需要考虑数据竞争问题,保证线程的安全性。
.
4、线程的概念以及与进程的区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
简而言之,
1)一个程序至少有一个进程,一个进程至少有一个线程.
-
线程的划分尺度小于进程,使得多线程程序的并发性高。
-
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
-
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
-
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
.
.
四、同步与互斥
1、同步和互斥概念
同步是指在多个线程之间协调执行的机制,通过同步,线程们按照一定的顺序执行,确保它们能够安全地访问共享的资源,避免同时对同一资源进行操作而导致数据的不一致。
互斥是同步机制的一个子集,指的是为了防止多个线程同时访问一个共享资源而使用的机制。通过互斥锁,一次只有一个线程可以访问资源,其他线程必须等待互斥锁释放后才能进入临界区。
同步是说进程的合作关系,互斥是说进程对资源的竞争关系。
在多线程编程中,同步和互斥是非常重要的概念,能够保证程序的正确性和稳定性。 Linux系统提供了许多同步和互斥的方法和工具,包括互斥锁、读写锁、条件变量、信号量等,开发者可以根据需要选择合适的方法进行编程。
2、同步互斥的方法
互斥锁
为了保护共享资源,使我们线程可以单独使用某个共享资源,使用之前先上锁,当其他进程要使用的时候,就需要等待到这个线程用完之后,再开锁。
声明
pthread_mutex_t m;
初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
上锁
int pthread_mutex_lock(pthread_mutex_t *mutex); 这个函数是阻塞型。
int pthread_mutex_trylock(pthread_mutex_t *mutex); 这个是非阻塞型的。
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
自旋锁
自旋锁与互斥锁功能一样,唯一一点不同的就是互斥锁阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待,直到得到锁。
接口上就是把互斥锁的mutex改成spin。
读写锁
读写锁也是用来控制互斥访问,特点是:
1、如果有线程读数据,则允许其它线程执行读操作,但不允许写操作;
2、如果有线程写数据,则不允许其它线程的读、写操作。
所以读写锁的规则就是:
1、如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁;
2、如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。
读写锁适合于对资源的读次数比写次数多得多的情况。
初始化:
int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);
申请读锁:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );
申请写锁:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );
非阻塞的方式获取写锁:
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
解锁:
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
销毁:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
信号量
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。与互斥锁在作用域上的区别:信号量: 进程间或线程间,互斥锁: 线程间。编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。
初始化:
int sem_init(sem_t *sem, int pshared, unsigned int value);
获取信号量:
int sem_wait(sem_t *sem);
超时获取信号量:
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
非阻塞方式获取信号量:
int sem_trywait(sem_t *sem);
释放信号量:
int sem_post(sem_t *sem);
获取信号量的值:
int sem_getvalue(sem_t *sem, int *sval);
销毁信号量
int sem_destroy(sem_t *sem);
五、共享与通信
1、多种进程间通信的方法介绍
共享内存
共享内存是进程间通信(IPC)的一种高效方式,在共享内存中,多个进程可以共享同一个物理内存区域,因此可以直接访问共享内存中的数据。但是多个进程在同时访问共享内存时需要进行同步,以防止数据不一致的情况发生。
1、共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
2、因为多个进程可以同时操作,所以需要进行同步。
3、信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
#include <stdio.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *shm_addr, int shmflg);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
.
.
消息队列
消息队列是一种进程间通信的方式,它能够实现进程之间的异步通信和数据传递。在消息队列中,每个消息都包含一个类型和一个数据部分,进程可以按照消息类型来选择需要接收的消息。
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
#include <stdio.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void* ptr, size_t size, long type, int flag);
// 控制消息队列:成功返回0, 失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds * buf);
信号量
信号量是一种用于进程间同步和通信的机制。进程可以使用 semget() 函数创建一个信号量,使用 semop() 函数对信号量进行操作。通过控制信号量的值,可以实现对临界资源的访问控制,从而保证多个进程对临界资源的访问是按照一定顺序进行的。
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
#include <stdio.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
管道(pipe)
管道(pipe)是用于进程间通信的一种简单的机制。一个进程将数据写入管道,另一个进程从管道中读取数据,通过这种方式可以实现数据传输和进程间通信。Linux 提供了匿名管道和有名管道两种方式,其中有名管道可以在不同进程之间共享。
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
#include <stdio.h>
int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1;
FIFO,也称为命名管道,是一种文件类型。
FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
#include <stdio.h>
int mkfifo(const char* pathname, mode_t mode);// 返回值:若成功返回0,失败返回-1;
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
.
.
套接字(socket)
前面说到的进程间的通信,所通信的进程都是在同一台计算机上的,而使用socket进行通信的进程可以是同一台计算机的进程,也是可以是通过网络连接起来的不同计算机上的进程。
双向通信:套接字通信允许在两个进程之间进行双向通信,即每个进程都能发送和接收数据。
面向连接:套接字通信是面向连接的,即在数据传输之前必须先建立连接,数据传输完成后要断开连接。
多种通信方式:套接字通信支持不同的通信方式,如TCP和UDP,可以根据应用的需要选择不同的通信方式。
灵活性:套接字通信非常灵活,可以在不同的网络环境下进行通信,如本地网络、Internet等。socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。
#include <stdio.h>
创建 :int socket(int domain, int type, int protocol);
绑定:int bind(int socket, const struct sockaddr *address, size_t address_len);
监听:int listen(int socket, int backlog);//服务端int accept(int socket, struct sockaddr *address, size_t *address_len);
连接:int connect(int socket, const struct sockaddr *address, size_t address_len);//客户端
数据通信:
int read(int socket, char *buffer, size_t len);int write(int socket, char *buffer, size_t len);
总结
linux系统平台本质上和freetos系统平台、ROS系统平台编程是一样的,关键是怎么使用系统平台资源编码
.
.