系统编程之线程管理

一、Linux 多线程简述 进程和线程的关系老生常谈。线程是最小的调度单位,进程是最小的资源分配单位。同一进程中的多个线程是在共享的内存空间中并发的多道执行路径,它们共享一个进程的资源。 对于Linux来说,Linux线程属于用户级线程,即线程的调度是在用户空间执行的。也就是说,Linux线程的实现是在内核之外的,多线程的概念对于内核来说并不是真实存在的,而只是通过线程库中的程序模拟的并发效果。 Linux线程遵循POSIX线程接口,称为pthread。pthread在其他平台也有对应的实现,如在windows。 二、线程操作 (1)库的使用 在开始多线程编程之前,需要说明一下 pthread.h 库。在编译使用pthread.h库的代码时,一般需要加-lpthread。pthread在glibc2.34之前是在glibc里面的,之后分出来变成一个单独的库,因此有的情况下,不加-lpthread也能编译成功。 (2)基本操作 创建线程 int pthread_create(pthread_t _Nullable * _Nonnull __restrict, const pthread_attr_t * _Nullable __restrict, void * _Nullable (* _Nonnull)(void * _Nullable), void * _Nullable __restrict); 该函数中第一个参数为指向一个线程标识变量的指针。第二个参数用来手动设置线程的各项属性,一般可以用NULL选择默认属性。第三个参数为一个函数指针,表示新建线程时需要执行的函数。注意该函数的参数类型和返回值类型,使用时需要进行强制类型转换。第四个参数为传递给函数的参数,也就是线程执行的函数的参数。不传递参数时可设置为NULL。 如下举一个创建线程的例子。 pthread_t tid; if (pthread_create(&tid, NULL, do_something, NULL)) { // error handler } 线程退出 void pthread_exit(void *ral_ptr); 当某一线程执行该函数时,会导致该线程结束。结束时会将ral_ptr指针传递给pthread_join 函数的 rval_ptr 线程取消 int pthread_cancel(pthread_t tid); 某一线程调用该函数,可以终止同一进程内的其他线程。 tid 即要终止的线程。 线程挂起 int pthread_join(pthread_t thread, void **rval_ptr); 某一线程调用该函数会阻塞该线程,直到参数 thread 所指示的线程退出。第二个参数为一个指向 pthread_exit 所设置的 ral_ptr 指针的指针。...

十二月 22, 2022 · 4 分钟 · 745 字 · Wokron

系统编程之进程间通信

一、进程间通信简述 进程是程序的一次运行的动态过程,为了完成一个任务,很多进程之间需要进行通信,从而相互合作以实现需要的功能。操作系统内核中提供了进程间通信的方法,主要有以下几种: 管道: 管道是最基本的进程通信机制,可以想象成一个管道,两端分别连着 2 个进程,一个进程往里面写,一个进程从里面读。如果读或写管道的时候没有内容可供读或写,进程将被阻塞,直到有内容可供读写为止。 消息队列: 消息队列本质上在内核空间中开辟了一块内存空间,这块内存是其他进程可以访问到的,在其中使用链表的方式实现了一个队列,进程可以向该队列中发送数据块或读取数据块,从而达到进程间通信的目的。其中每个数据块包含两部分,首先是一个类型为 long 的 type,然后是具体的数据,数据块的 type 可以作为进程之间相互约定好的协议。例如一个进程发送 type 为123的消息,另一个进程接收 type 为123 的消息,后者便可确认这就是前者发送的信息,并信任该数据块中的数据。 信号量: 不同进程之间存在对资源的竞争,信号量就是用来标明可用资源的数量的数据结构,本质是为了实现多个进程之间的同步。需要注意,信号量(semaphore)与 “信号”(signal)没有关系。 共享内存: 共享内存的本质就是把两个或多个进程的虚拟地址映射到同一块物理内存。这样,一个进程通过对这块内存的读写就能被其他进程访问到,从而实现进程间通信的功能。 二、进程间通信操作 (1)准备操作 获取 key #include <sys/types.h> #include <sys/ipc.h> key_t ftok( const char * fname, int id ); 共享内存,消息队列,信号量等进程间通信方式都需要寻找一个中间介质来进行通信。不同的介质需要用不同的信息来进行区分,这就是进程间通信的 key。ftok() 函数就可以生成一个唯一的 key,该函数获取一个文件路径和一个字序号,生成一个用于区分的 key。 注意,选择文件路径只是因为文件的编号是独有的。设置的文件路径与代码和程序并没有什么关系。 命令管理进程间通信 若没有调用控制函数进行删除,则已分配的进程间通信不会自动释放。如果共享内存,消息队列,信号量在新进程执行时依旧有之前残留的信息,可能导致程序运行结果错误。可以通过 ipcs 和 ipcrm 命令进行管理。 ipcs # 显示所有进程间通信信息 ipcrm -q MsgID # 删除消息队列 ipcrm -s SemID # 删除信号量 ipcrm -m ShrID # 删除共享内存 (2)消息队列 消息队列获取 #include <sys/types....

十一月 16, 2022 · 5 分钟 · 962 字 · Wokron

系统编程之信号及信号处理

一、信号简介 (1)信号含义 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟;在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用 kill 发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。 (2)信号分类 可以使用kill -l命令查看当前系统支持的所有信号: 信号值小于 SIGRTMIN(<=34)的信号都是不可靠信号。它的主要问题是信号可能丢失。 信号值位于 SIGRTMIN 和 SIGRTMAX 之间的信号都是可靠信号,这些信号支持排队,不会丢失。 (3)信号的产生 信号可以由一下几种方式产生: 键盘事件:ctrl+c ctrl+\ ctrl+Z 等 非法内存:如果内存管理出错,系统就会发送一个信号进行处理 硬件检测到异常:如段错误,除 0,总线错误等 环境切换:比如说从用户态切换到其他态,状态的改变也会发送一个信号,这个信号会告知给系统 系统调用:如调用kill,raise,sigsend ,sigqueue函数等 (4)信号处理 进程可以通过三种方式响应信号: 接受默认处理 忽略信号(某些信号不能被忽略,如 SIGKILL 和 SIGSTOP) 捕捉信号并执行信号处理程序 二、信号操作 (1)信号发送 系统调用中用于发送信号的函数有 kill() raise() abort() 等。 kill() 函数 #include <signal.h> int kill(pid_t pid, int sig); //第一个参数pid代表接受信号的进程PID,第二个参数代表要发送的信号 参数 pid 会影响 kill()函数的作用,取值分为以下四种情况 若 pid>0,则发送信号 sig 给进程号为 pid 的进程。 若 pid=0,则发送信号 sig 给当前进程所属进程组的所有进程。 若 pid=-1,则发送信号 sig 给除 1 号进程和当前进程外的所有进程。 若 pid<-1,则发送信号 sig 给属于进程组 pid 的所有进程。 segqueue() 函数 sigqueue()函数支持发送信号的同时传递参数,需要配合 sigaction() 函数一起使用。...

十一月 13, 2022 · 3 分钟 · 584 字 · Wokron

系统编程之进程管理

一、引言 进程是操作系统中的重要概念,是对执行一定功能的程序的过程的抽象。这篇文章将简要说明进程的相关知识。介绍进程管理相关的函数,并通过这些函数实现重定向和进程间通信等功能。 二、进程简介 1. 程序执行原理 程序在编译后以二进制方式存在于外存上,执行的时候被操作系统载入内存。以 Linux 系统上的 C 语言编译出来的程序为例,载入的过程简单来说就是把编译完成的 ELF (Executable and Linkable Format 可执行与可链接格式) 文件的几个段的内容读取到内存指定位置,然后初始化寄存器的内容,将指令寄存器(比如cs:ip)指向程序入口,再初始化一些进程相关内容就完成了。 在某一次时钟中断发生的时候,进程主动陷入内核态,进行进程切换的系统调用,CPU 将切换到另一个进程工作。总而言之,整个计算机从开机到关机,就是一个不断创建、切换、终止进程的过程。 2. 进程概念的用途 早期的计算机一次只能执行一个程序,这种程序完全控制系统,并且访问所有系统资源。相比之下,现代计算机系统允许“同时”加载多个应用程序到内存,以便并发(轮流)执行。 这种改进要求对各种程序提供更严的控制和更好的划分。这些需求导致了进程概念的诞生。 进程是现代分时操作系统的工作单元,是操作系统向运行中的程序进行资源分配的单位。进程包括程序代码(文本),当前活动(程序计数器,寄存器的值),堆栈,数据端,堆。 需要注意区分程序和进程的概念。程序是被动实体,如存储在磁盘上的可执行文件;进程是活动实体,具有一个程序计数器用于表示下个执行命令和一组相关资源。 当一个可执行文件被加载到内存时,这个程序就成为进程。 两个进程可以与同一程序相关联,但当作两个单独的执行序列,虽然文本段相同,但是数据、堆、堆栈不同。 三. 进程管理 接下来介绍使用操作系统 API 进行进程管理的方法。 1. 使用 fork 创建新进程 #include <unistd.h> pid_t fork(); fork 无参数,返回一个用于指示子进程的 pid(对于子进程,返回值为 0)。其作用是创建一个子进程,共享父进程所有内容,并且这个子进程会接着 fork 下面的代码继续执行。fork有以下两种用法: 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。 一个进程要执行一个不同的程序。在这种情况下,子进程从fork返回后立即调用exec。 如果在调用 fork 后子进程先于父进程结束,则子进程就会变为僵尸进程,虽然结束,却依然占据了进程表中的一个位置。为了避免这种情况,需要调用 wait 或 waitpid 来使父进程等待子进程结束,并释放子进程的信息。 #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid,int *status,int options); 下面将以一个程序作为例子。该程序由父进程创建两个子进程,父进程打印字符 B ,两个子进程分别打印 A 和 C ,并且要使最终的输出为 ABC 。...

十月 31, 2022 · 4 分钟 · 651 字 · Wokron

系统编程之 Shell 编程

一、前言 本文将简单探索shell脚本编程,介绍shell的基本语法。 二、shell简介 shell是一个命令解释器,可以用来启动、停止、编写程序;是用户和UNIX/Linux操作系统内核程序间的一个接口。 而shell编程则是将linux命令与shell的各种流程控制和条件判断来组合成命令与变量,形成可以进行自动处理的脚本程序。 三、前期准备 创建脚本 shell脚本是一个文本文件,可用文本编辑器如vi、vim编辑保存。创建shell脚本只需按照创建文本文件的方式创建。如 vi c1.sh vim c2.sh > c3.sh shell脚本一般以.sh为后缀,但没有后缀依旧可以执行。 创建的shell脚本,一定要在开头第一行加上如下语句: #!/bin/bash 这一行将指明该脚本执行所需要的命令解释器。 执行脚本 shell脚本的执行方法有 sh <scriptname> bash <scriptname> 或者使用chmod命令修改脚本为可执行,再直接使用 ./<scriptname> 运行。 四、基本语法 变量 shell中的变量分为环境变量、用户定义变量、内部变量。 其中环境变量是操作系统的一部分,但可以利用shell脚本进行修改;用户变量即在脚本中声明的变量;而内部变量则用来指示脚本运行中出现的一些变量。 声明 只有用户变量可以声明。和其他语言一样,使用等号进行声明。但要注意的是,shell脚本是弱类型的,因此变量名前不需要加上类型名。 var=hello_world 注意shell对空格敏感,声明时等号两边不能有空格 另外可以在变量名前添加 readonly关键字设为只读 readonly constVar shell中声明数组同样直接写出数组名称 arr[0]=1 arr[1]=5 arr[10]=20 未赋值的部分默认为NULL。 注意 ubuntu 默认使用 dash 而非 bash shell。dash 并不支持数组。要使用数组可以用bash运行脚本,即运行命令 bash <scriptname> 赋值 shell变量有类似左值右值的区别。在用其他变量进行赋值时,需要对变量使用${ } 进行取值。 var1=hi var2=${var1} 引号 shell脚本是为了自动化处理命令而设计的。因此语法中有很大一部分关注于字符串和命令的相关操作。在变量上体现在,shell中所有变量默认以字符串形式存在。 并且,为了满足命令处理的需要,shell设计出了引号变量值。 shell中的引号包括单引号、双引号和倒引号。 单引号中的字符均作为普通字符出现。(可以包括空格) var1=hello_world # var2=hello world # 不合法 var3='hello world' 双引号中的字符大部分作为普通字符对待。除了$\’和双引号,这些变量依旧用于对字符串内容进行变量替换。...

十月 8, 2022 · 2 分钟 · 321 字 · Wokron

系统编程之命令行编译

一、前言 本文将简单介绍在Linux系统下的命令行编译流程。介绍gcc、gdb、make等工具的简单使用。 二、GCC 基本操作 编译选项 无选项编译链接 用法:gcc test.c 作用:将 test.c 预处理、编译、汇编并链接形成可执行文件。这里未指定输出文件,默认输出为 a.out。 选项 -o 用法:gcc test.c -o test 作用:将 test.c 预处理、编译、汇编并链接形成可执行文件 test。-o 选项用来指定输出文件的文件名。 选项 -E 用法:gcc -E test.c -o test.i 作用:将 test.c 预处理输出 test.i 文件。 选项 -S 用法:gcc -S test.i 作用:将预处理输出文件 test.i 编译成 test.s 文件。 选项 -c 用法:gcc -c test.s 作用:将汇编语言文件 test.s 汇编成目标代码 test.o 文件。 无选项链接 用法:gcc test.o -o test 作用:将目标代码文件 test.o 链接成最终可执行文件 test。 选项 -O 用法:gcc -O1 test.c -o test 作用:使用编译优化级别 1 编译程序。级别为 1~3,级别越大优化效果越好,但编译时间越长。 官方文档:GCC, the GNU Compiler Collection...

九月 12, 2022 · 3 分钟 · 589 字 · Wokron