Linux基础(二)——多进程开发
一、进程概述

进程是指在计算机中正在运行的程序实体,它包含了程序代码、数据和执行状态等信息。进程是动态的,它需要占用计算机的资源,例如内存、CPU 时间、I/O 等。
因此,程序和进程之间的关系可以类比为蓝图和建筑物的关系。程序就像是蓝图,描述了建筑物的设计和构造,而进程就像建筑物本身,是根据蓝图所描述的设计和构造而实际建造出来的。
总之,程序是静态的,而进程是动态的。程序需要被加载到内存中并被操作系统管理成为进程,才能够在计算机上运行。

单道程序设计是指在计算机系统中每次只能运行一个程序,程序运行结束后才能运行下一个程序。这种方式需要用户手动将程序输入计算机,计算机完成一个程序的执行后,用户再将下一个程序输入计算机。这种方式效率低下,且无法充分利用计算机资源。
而多道程序设计是指在计算机系统中同时运行多个程序,这些程序可以共享计算机的资源(如CPU、内存等),通过操作系统的调度算法来控制各个程序的执行顺序和资源使用。多道程序设计能够更好地利用计算机资源,提高计算机系统的吞吐量和响应速度。
在多道程序设计中,操作系统将所有需要执行的程序分成多个作业,每个作业都包含一个或多个独立的程序,这些程序可以并发执行。作业之间的切换由操作系统负责,操作系统会根据程序的优先级、资源需求等因素来分配计算机资源,以达到最优的执行效果。
总之,单道程序设计和多道程序设计是两种不同的程序设计方式,前者每次只能运行一个程序,效率低下,而后者可以同时运行多个程序,提高计算机系统的效率和吞吐量。

时间片的引入可以使得多个进程在同一时间内并发运行,并且可以让每个进程都有足够的机会使用CPU资源。时间片的大小通常由操作系统根据系统负载、进程优先级等因素动态调整,以保证系统的响应速度和吞吐量。
使用时间片的优点是:
提高系统的并发性和响应速度,避免进程长时间等待资源。
充分利用CPU资源,提高系统的吞吐量。
可以让每个进程都有机会使用CPU资源,避免某些进程长时间占用CPU,导致其他进程无法运行。
但是,时间片过小会导致进程频繁切换,增加系统开销;时间片过大会导致进程响应速度变慢,影响用户体验。因此,操作系统需要根据实际情况动态调整时间片的大小,以达到最优的系统性能。

并行(Parallel)指的是多个任务同时执行,即多个任务在同一时刻同时进行。在Linux中,如果系统有多个CPU或CPU核心,那么多个进程或线程可以在不同的CPU或核心上同时执行,从而实现并行处理,提高系统的处理速度和吞吐量。
并发(Concurrency)指的是多个任务交替执行,即多个任务在一段时间内交替进行。在Linux中,多个进程或线程可以在同一CPU或核心上交替执行,从而实现并发处理。在并发处理中,操作系统会根据调度算法来决定哪个进程或线程优先执行,从而实现任务的调度和管理。
可以看出,并行和并发都是多任务处理的方式,但是它们的实现方式不同。并行需要多个CPU或核心的支持,而并发可以在单个CPU或核心上实现。并行处理可以提高系统的处理能力和效率,但需要硬件支持;而并发处理可以提高系统的响应速度和资源利用率,但需要考虑并发访问带来的同步和竞争问题。
总之,并行和并发是两种不同的多任务处理方式,它们都有着自己的优点和适用场景,需要根据实际情况选择合适的方式来进行多任务处理。

在Linux中,PCB是由内核动态分配的,当进程创建时,内核会为进程分配一块内存,用来存储PCB信息。每个PCB都有一个唯一的进程ID,用来标识该进程。当进程执行时,内核会根据PCB中的信息来管理和控制进程的行为,例如调度进程、分配资源、进行系统调用等等。
Linux中的PCB包含了很多信息,这些信息可以分为以下几个方面:
进程标识信息:进程ID、进程优先级、进程所属用户等。
进程状态信息:进程状态、进程调度信息、CPU时间等。
进程内存信息:进程地址空间、虚拟内存映射等。
进程文件信息:进程打开的文件、文件描述符等。
通过PCB,内核可以对进程进行管理和控制,例如对进程进行调度、分配资源、中断处理等。同时,PCB也是进程之间通信和同步的重要手段,进程可以通过PCB来访问和修改其他进程的信息,实现进程之间的通信和同步。
二、进程状态转换

新建状态(New):当一个进程被创建时,它处于新建状态。此时,操作系统会为该进程分配资源并初始化进程控制块(PCB)。
就绪状态(Ready):当进程已经准备好运行,但由于CPU资源正在被其他进程占用,因此它不能立即运行,此时进程处于就绪状态。
运行状态(Running):当进程被分配到CPU资源并开始执行时,它处于运行状态。此时进程正在使用CPU资源进行计算、I/O操作等。
阻塞状态(Blocked):当进程需要等待某些事件发生(如I/O操作完成、信号到达等),而这些事件不能立即发生时,进程会进入阻塞状态。此时,操作系统会将进程从CPU资源中移除,直到事件发生后才会重新将其调入就绪状态。
终止状态(Terminated):当进程执行完成或被强制终止时,它会进入终止状态。此时,操作系统会释放进程占用的资源,并从系统进程列表中删除该进程。
在进程的生命周期中,进程状态会不断地发生变化,通常的状态转换为:
New -> Ready -> Running -> Blocked -> Ready -> Running -> ... -> Terminated
其中,就绪状态、运行状态和阻塞状态都是进程运行的三种基本状态,它们之间的切换由操作系统的调度算法来决定,以实现对进程的管理和调度。
总之,Linux中进程状态转换是进程生命周期中重要的一部分,了解进程状态的变化可以帮助我们更好地理解进程的执行过程和操作系统的工作原理。

top:用于实时监控进程的状态和系统资源使用情况,可以显示CPU、内存、I/O等信息。
kill:用于向进程发送信号,可以用来终止进程、重启进程、暂停进程等。
pkill:用于根据进程名或其他属性杀死进程,可以通过正则表达式等方式指定进程。
killall:用于根据进程名杀死进程,可以杀死所有同名进程。
nice:用于设置进程的优先级,可以让进程使用更多或更少的CPU资源。
renice:用于修改已经运行的进程的优先级。
top、htop:用于监控系统资源使用情况和进程状态,可以实时显示进程的CPU、内存、I/O等信息。
vmstat:用于监控系统资源使用情况,可以显示CPU、内存、磁盘、网络等信息。
pidof:用于根据进程名获取进程ID。

在Linux中,进程号的获取和管理可以通过以下几个系统调用函数来实现:
getpid():获取当前进程的进程ID。
getppid():获取当前进程的父进程ID。
fork():创建一个新的进程,新进程的进程ID不同于父进程的进程ID。
exec():在当前进程中执行一个新的程序,进程ID不会改变。
wait():等待指定进程退出,获取该进程的退出状态。
kill():向指定进程发送信号,可以用来终止进程、重启进程、暂停进程等。
signal():设置或处理信号处理函数,可以用来处理进程接收到的信号。
除了以上这些系统调用函数,还有一些其他的函数可以用来获取和管理进程信息,例如:
ps:用于显示当前系统中的进程信息,包括进程ID、进程状态等。
top:用于实时监控进程的状态和系统资源使用情况。
htop:类似于top,但是可以使用鼠标和键盘进行交互。
pstree:用于显示进程之间的关系,以树状结构的形式展示。
lsof:用于显示系统中打开的文件和进程信息。
通过这些函数和命令,我们可以获取和管理进程信息,了解系统的运行状态和资源使用情况,进行调试和优化。
三、进程创建

当进程调用fork()时,操作系统会为子进程创建一个新的进程描述符,并为其分配一个唯一的进程ID。子进程将继承父进程的打开文件描述符、信号处理程序等信息。父进程和子进程之间的区别在于返回值:在父进程中,fork()将返回子进程的进程ID,而在子进程中,返回值将为0。
在子进程中,通常紧接着会调用exec()系列函数,以便将子进程替换为另一个可执行文件。这将导致子进程的地址空间被新程序所占用。
四、父子进程虚拟地址空间情况

在创建子进程时,操作系统将为子进程复制父进程的整个虚拟地址空间,包括代码段、数据段、堆和栈等内容。子进程的虚拟地址空间是父进程虚拟地址空间的一个副本,但是子进程的虚拟地址空间是独立的,因此父进程和子进程之间不会相互干扰。
当一个进程修改它的虚拟地址空间时,它的修改只会影响到它自己的虚拟地址空间,不会影响到其他进程的虚拟地址空间。例如,当一个进程分配内存时,它只会修改自己的虚拟地址空间,不会影响其他进程的虚拟地址空间。因此,每个进程都可以独立地管理它自己的虚拟地址空间,这是多个进程能够同时运行的重要基础。
在父进程和子进程中,相同的虚拟地址通常都会映射到相同的物理内存区域。这意味着,如果一个进程修改了它的虚拟地址空间中的某个变量,那么另一个进程也会看到这个变量的值发生了改变。但是,由于每个进程都有自己的虚拟地址空间,因此它们之间的变量并不是真正共享的,它们只是在物理内存中的同一位置上有相同的副本而已。
五、父子进程关系及GDB多进程调试


parent:跟踪父进程,子进程将在后台运行;
child:跟踪子进程,父进程将在后台运行;
ask:每次fork()调用时询问用户应该跟踪哪个进程。
默认情况下,GDB使用parent选项来跟踪进程。如果要跟踪子进程,可以使用set follow-fork-mode child命令。
在GDB中进行多进程调试时,可以使用set detach-on-fork命令来控制子进程的行为。如果将其设置为off,则子进程将继续在GDB中运行;如果将其设置为on,则子进程将自动与GDB断开连接,并在后台运行。默认情况下,set detach-on-fork命令的值为on。
在多进程调试中,可以使用info inferiors命令来列出所有的进程。使用inferior命令可以选择当前要调试的进程。在选择了进程后,可以使用info threads命令来列出该进程中的所有线程。使用thread命令可以选择要调试的线程。在选择了线程后,可以像调试单个进程一样使用GDB进行调试。
六、exec函数族

exec()函数族包括以下函数:
int execl(const char *path, const char *arg, ...);:通过指定可执行文件的路径和命令行参数,执行一个新程序。arg参数和后续的可变参数都是新程序的命令行参数,最后一个参数必须是NULL。
int execv(const char *path, char *const argv[]);:与execl()函数类似,但是参数以字符串数组的形式传递给新程序。
int execle(const char *path, const char *arg, ..., char *const envp[]);:与execl()函数类似,但是可以指定新程序的环境变量。
int execve(const char *path, char *const argv[], char *const envp[]);:与execv()函数类似,但是可以指定新程序的环境变量。
int execlp(const char *file, const char *arg, ...);:与execl()函数类似,但是可执行文件的路径不需要指定绝对路径,而是在PATH环境变量中搜索可执行文件。
int execvp(const char *file, char *const argv[]);:与execv()函数类似,但是可执行文件的路径不需要指定绝对路径,而是在PATH环境变量中搜索可执行文件。
exec()函数族的返回值只有在执行失败时才会返回,如果执行成功,则不会返回。如果exec()函数执行失败,它将返回-1,并设置errno变量来指示错误的类型。
注意,在exec()函数族中,新程序将会继承当前进程的一些属性,例如文件描述符和信号处理程序等。因此,在调用exec()函数族之前,通常需要使用fork()函数创建一个新进程,以便在子进程中调用exec()函数族,从而保持父进程不受影响。
七、进程退出、孤儿进程、僵尸进程



僵尸进程并不会占用系统资源,但是它们会占用一定的进程ID和进程描述符等资源。如果系统中有大量的僵尸进程,就会导致系统资源的浪费。为了避免僵尸进程的产生,父进程通常需要在子进程退出时调用wait()或waitpid()函数来获取它的退出状态信息,并且释放子进程的资源。如果父进程不再需要子进程的状态信息,也可以使用waitpid()函数的WNOHANG选项来非阻塞地获取子进程的状态信息。如果父进程不希望等待子进程退出,也可以使用SIGCHLD信号来异步地处理子进程的退出状态信息。
八、wait函数

Linux中的进程回收是通过父进程调用wait()或waitpid()函数来实现的。这些函数会阻塞父进程,直到一个子进程退出,然后返回子进程的进程ID和退出状态信息。父进程可以使用这些信息来释放子进程的资源,并且避免子进程成为僵尸进程。如果父进程不希望阻塞,也可以使用waitpid()函数的WNOHANG选项来非阻塞地获取子进程的状态信息。
当一个进程退出时,它的退出状态信息会被保存在内核中,直到它的父进程调用wait()或waitpid()函数来获取。如果一个进程没有父进程,或者其父进程已经退出,那么这个进程就会被init进程接管,init进程将会周期性地调用wait()函数来获取孤儿进程的退出状态信息,并且释放它们的资源。
进程回收还包括释放进程占用的内存空间和其他资源。当一个进程退出时,它占用的内存空间会被操作系统回收,这些空间可以被分配给其他进程使用。同时,打开的文件描述符等其他资源也会被释放,以便下一个进程可以使用它们。
总之,进程回收是Linux系统中一个重要的机制,它可以帮助操作系统有效地管理进程和资源,从而提高系统的稳定性和性能。
九、waitpid函数

WEXITSTATUS(status):如果进程正常退出,则返回进程的退出状态码。
WIFSIGNALED(status):如果进程是因为信号而退出,则返回非零值。
WTERMSIG(status):如果进程是因为信号而退出,则返回导致进程退出的信号编号。
WCOREDUMP(status):如果进程是因为信号而退出,并且产生了核心转储文件,则返回非零值。
WIFSTOPPED(status):如果进程被暂停,则返回非零值。
WSTOPSIG(status):如果进程被暂停,则返回导致进程暂停的信号编号。
WIFCONTINUED(status):如果进程被恢复,则返回非零值。

status参数是一个整型指针,用于保存子进程的退出状态信息。如果不关心子进程的退出状态信息,可以将status设置为NULL。
options参数指定等待子进程的行为。常用的选项包括:
WNOHANG:非阻塞模式,即如果没有子进程退出,则立即返回0,而不会阻塞。
WUNTRACED:如果子进程处于暂停状态,则也返回状态信息。
WCONTINUED:如果子进程被恢复,则也返回状态信息。
waitpid()函数的返回值是退出的子进程的进程ID,如果没有子进程退出,则返回0。如果出错,则返回-1,并设置全局变量errno表示错误原因。
十、进程间通信简介

管道(Pipe):管道是一种半双工的通信方式,它只能在具有父子关系的进程之间使用。管道有两种类型:有名管道和无名管道。有名管道可以在不具有父子关系的进程之间使用,而无名管道只能在具有父子关系的进程之间使用。
命名管道(FIFO):命名管道是一种有名管道,它允许不具有父子关系的进程之间进行通信。命名管道可以在文件系统中创建一个特殊文件来实现。
信号(Signal):信号是一种异步通信方式,它允许进程向其他进程发送信号,以通知它们发生了某些事件。信号可以用于进程间的同步和异步通信。
共享内存(Shared Memory):共享内存是一种通过共享内存区域来实现进程间通信的方式,多个进程可以访问同一个内存区域,从而实现数据共享。共享内存通常需要配合信号量来进行同步和互斥操作,以保证数据的一致性和安全性。
消息队列(Message Queue):消息队列是一种通过队列来实现进程间通信的方式,发送进程将消息放入队列中,接收进程从队列中取出消息。消息队列可以用于进程之间的异步通信。
信号量(Semaphore):信号量是一种用于进程同步和互斥的机制,它可以控制多个进程对共享资源的访问。信号量通常用于共享内存和消息队列等进程间通信方式中。

套接字(Socket):套接字是一种通用的进程间通信方式,它可以在不同主机之间进行通信。套接字通常使用TCP或UDP协议来实现数据传输。
远程过程调用(RPC):RPC是一种通过网络调用远程计算机上的程序的机制。它可以让不同主机之间的进程像调用本地程序一样调用远程程序。
远程方法调用(RMI):RMI是一种Java语言特有的进程间通信方式,它允许Java程序在不同主机之间进行通信。
十一、匿名管道概述


父子进程通信:管道只能在具有父子关系的进程之间使用。这是因为管道是由一个进程创建的,另一个进程只能通过继承文件描述符的方式来使用该管道。
数据传输有序:管道中的数据传输是有序的,即写入管道中的数据会按照先后顺序被读取出来。在同一时刻,只有一个进程可以对管道进行读写操作,因此不会出现数据交错的情况。
数据传输有限制:管道有一个固定的缓冲区大小,当缓冲区已满时,写入进程会被阻塞,直到有足够的空间可用。同样地,当缓冲区为空时,读取进程也会被阻塞,直到有数据可用。
匿名管道:在Linux中,管道分为匿名管道和命名管道两种形式。匿名管道没有文件名和文件系统中的实体,只能在创建它的进程和其子进程之间使用。


文件描述符:管道的文件描述符是一个包含两个整数的数组,其中一个整数用于读取数据,另一个整数用于写入数据。当进程打开管道时,会返回一个文件描述符,该文件描述符可以用于读取或写入管道中的数据。
管道的状态:管道的状态包括一些基本的属性,如缓冲区大小、读取进程数量、写入进程数量等。在Linux中,可以使用fstat()系统调用来获取管道的状态信息。
管道的锁:由于管道是多个进程共享的资源,因此需要使用锁来保护管道的操作。在Linux中,可以使用互斥锁和条件变量来保护管道的读写操作。
十二、父子进程通过匿名管道通信

创建子进程:在父进程中,可以使用fork()系统调用来创建一个新的子进程。子进程会继承父进程的文件描述符。
关闭不需要的文件描述符:在子进程中,由于子进程也继承了父进程的文件描述符,因此需要关闭不需要的文件描述符,即写入文件描述符或读取文件描述符。
进行通信:在父进程中,可以使用写入文件描述符向管道中写入数据;在子进程中,可以使用读取文件描述符从管道中读取数据。
关闭文件描述符:在进程使用完文件描述符后,需要将其关闭,以释放系统资源。
十三、匿名管道通信案例

2. 创建子进程:通常,匿名管道是由一个父进程和一个子进程之间进行通信。因此,父进程将创建一个子进程来与其通信。在创建子进程时,父进程需要将管道文件描述符复制到子进程的文件描述符中。
3. 进程通信:在子进程中,它可以通过管道的写入端将数据写入管道。父进程可以从管道的读取端读取这些数据。同样,父进程也可以通过管道的写入端将数据写入管道,而子进程可以从管道的读取端读取这些数据。
4. 关闭管道:当进程完成通信时,它们都应该关闭管道。在父进程和子进程中,当读取到管道尾部时,也应该关闭管道。
十四、管道的读写特点和管道设置为非阻塞

1. 打开需要设置为非阻塞的管道
2. 使用fcntl函数获取管道的当前标志位,可以使用F_GETFL命令来实现
3. 将获取到的标志位与O_NONBLOCK位进行或操作,以设置管道为非阻塞模式
4. 使用fcntl函数将设置后的标志位设置回管道中,可以使用F_SETFL命令来实现
十五、有名管道介绍及使用

1. 有名管道是在文件系统中存在的文件,并且可以在多个进程之间共享。
2. 有名管道可以用于不相关的进程之间的通信,而匿名管道只能在父子进程之间使用。
3. 有名管道的创建和删除是由文件系统完成的,而匿名管道只是由进程创建的一组文件描述符。
4. 有名管道可以用于实现进程间的同步。

1. 有名管道是在文件系统中存在的文件,并且可以在多个进程之间共享。
2. 有名管道可以用于不相关的进程之间的通信,而匿名管道只能在父子进程之间使用。
3. 有名管道的创建和删除是由文件系统完成的,而匿名管道只是由进程创建的一组文件描述符。
4. 有名管道可以用于实现进程间的同步。

1. FIFO是一种阻塞式I/O,当没有数据可用时,读取和写入FIFO的进程都会阻塞。
2. 在写入FIFO之前,必须有一个进程打开FIFO进行写入操作。同样,在读取FIFO之前,必须有一个进程打开FIFO进行读取操作。
3. FIFO通常是用于单向通信的(例如,从一个进程读取数据,或将数据写入另一个进程)。如果需要进行双向通信,通常需要创建两个FIFO,一个用于读取,另一个用于写入。
4. 当不再需要FIFO时,必须显式地删除它。这可以通过使用rm命令或unlink()系统调用来完成。
FIFO是一种非常有用的IPC机制,可用于在不同的进程之间进行通信。在Linux中,FIFO通常被用于实现多个进程之间的协作和同步。

十六、有名管道实现简单版聊天功能

十七、内存映射

在Linux中,内存映射主要通过mmap()系统调用来实现。

内存限制:mmap将文件映射到内存中,因此需要注意内存的使用量。如果文件很大,可能会导致内存不足,从而导致系统性能下降或崩溃。
文件访问权限:使用mmap需要确保文件具有适当的访问权限。如果文件不可读或不可写,将无法使用mmap。
内存对齐:mmap将文件映射到内存时需要进行内存对齐,通常情况下是按页面大小(通常为4KB)进行对齐。因此,在使用mmap时需要确保访问的内存地址与文件偏移量是页面对齐的。
文件修改:如果使用mmap进行写操作,需要注意对文件进行同步操作,以确保数据已经写入磁盘。否则,如果系统崩溃或出现其他异常情况,可能会导致数据丢失或损坏。
跨平台兼容性:在跨平台使用mmap时需要注意不同操作系统的实现差异,例如内存对齐方式和文件访问权限等。需要确保代码能够在不同的操作系统上正确运行。
错误处理:使用mmap时需要注意错误处理,例如文件不存在、内存映射失败等情况。需要根据具体情况进行适当的处理,以避免程序出现崩溃等问题。

十八、信号概述

信号的目的是在运行中的进程之间传递信息,例如通知进程某个事件的发生,向进程发送中断请求等。Linux系统中定义了多种信号,每个信号都有一个唯一的编号和特定的含义。例如,SIGINT信号表示中断进程,通常是由用户在终端上按下Ctrl+C键发送的。
信号的特点如下:
异步事件:信号是异步事件,可以在任何时刻被发送和接收。进程无法预测何时会接收到信号,因此需要谨慎处理。
中断处理:当进程接收到信号时,操作系统中断进程的正常执行流程,转而执行信号处理函数。信号处理函数通常是一段短小的代码片段,用于处理特定的事件。
优先级:在Linux系统中,信号具有优先级。当多个信号同时发送给进程时,系统会根据信号的优先级确定信号的处理顺序。
可靠性:信号的发送和接收都是可靠的。如果进程向其他进程发送信号,系统会保证信号被传递到目标进程。如果进程接收到一个信号,系统会保证该信号不会丢失。

SIGHUP:挂起信号,通常在终端会话结束时发送给进程。
SIGINT:中断信号,通常由用户在终端上按下Ctrl+C键发送给进程。
SIGQUIT:退出信号,通常由用户在终端上按下Ctrl+\键发送给进程。
SIGILL:非法指令信号,通常表示进程执行了不合法的指令或操作。
SIGABRT:终止信号,通常由程序自身调用abort函数发送给自己,表示程序出现了致命错误。
SIGFPE:浮点异常信号,通常表示进程执行了一条非法的浮点运算指令。
SIGKILL:杀死信号,可以立即终止进程的运行,无法被忽略或捕获。
SIGSEGV:段错误信号,通常表示进程试图访问未分配或不允许访问的内存地址。
SIGPIPE:管道破裂信号,通常表示进程试图向已关闭的管道写入数据。
SIGALRM:闹钟信号,通常用于实现定时器功能。
SIGTERM:终止信号,可以请求进程正常退出,并允许进程进行清理操作。
SIGUSR1/SIGUSR2:用户定义信号,通常由进程自定义使用。
SIGCHLD:子进程状态改变信号,通常由父进程接收,用于通知父进程子进程的状态改变。
SIGCONT:继续信号,通常用于继续之前被暂停的进程。
SIGSTOP:停止信号,可以暂停进程的运行,无法被忽略或捕获。
SIGTSTP:挂起信号,通常由用户在终端上按下Ctrl+Z键发送给进程,用于将进程暂停并放入后台。

Ign(忽略):当进程接收到该信号时,进程会直接忽略该信号,继续执行。
Core(生成核心转储文件):当进程接收到该信号时,进程会生成一个核心转储文件,用于调试和分析程序错误。
Stop(停止进程,默认处理动作):当进程接收到该信号时,进程会被暂停,并等待被其他进程继续执行。
Cont(继续进程):当进程接收到该信号时,进程会从之前被暂停的地方继续执行。
十九、kill、raise、abort函数

int kill(pid_t pid, int sig);其中,pid参数指定要发送信号的进程或进程组的ID,sig参数指定要发送的信号编号。raise函数:可以向当前进程发送信号。其函数原型为:int raise(int sig);其中,sig参数指定要发送的信号编号。abort函数:可以向当前进程发送SIGABRT信号,使其自行终止。其函数原型为:void abort(void)。需要注意的是,使用kill、raise和abort函数向进程发送信号时,需要注意信号的含义和作用,避免信号的误用导致不必要的麻烦或错误。同时,对于一些特殊的信号,例如SIGKILL和SIGSTOP,无法被忽略或捕获,因此需要谨慎使用。
二十、alarm函数

二十一、setitimer定时器函数

ITIMER_REAL:使用系统的实时时钟来计时,定时器到期时发送SIGALRM信号。
ITIMER_VIRTUAL:使用进程的虚拟时钟来计时,定时器到期时发送SIGVTALRM信号。
ITIMER_PROF:使用进程和系统的时间来计时,定时器到期时发送SIGPROF信号。
new_value参数是一个指向结构体itimerval的指针,用于设置新的定时器值。该结构体包含了两个成员:
it_interval:定时器的周期时间,即定时器到期后再次触发的时间间隔。
it_value:定时器的初始时间,即定时器第一次到期的时间间隔。当定时器到达指定时间间隔时,操作系统会向当前进程发送相应的信号,可以通过信号处理函数来处理该信号。需要注意的是,使用setitimer函数设置定时器时,需要确保定时器的设置和取消符合预期,避免产生不必要的麻烦或错误。同时,对于一些特殊的信号,例如SIGKILL和SIGSTOP,无法被忽略或捕获,因此需要谨慎使用。
二十二、signal信号捕捉函数

另外,为了避免信号处理函数与其他代码产生竞争条件,通常建议在信号处理函数中尽量避免使用不可重入的函数,例如printf和malloc等。如果必须使用这些函数,可以使用异步信号安全函数(async-signal-safe function)来代替。异步信号安全函数是指可以在信号处理函数中安全使用的函数,它们不会产生竞争条件,也不会因为中断而导致不确定的行为。常见的异步信号安全函数包括write、_exit、read、fcntl等。
二十三、信号集及相关函数

可以使用以下函数来操作信号集:
sigemptyset函数:用于清空信号集,将所有信号从信号集中移除。sigfillset函数:用于将所有信号加入信号集中。sigaddset函数:用于将指定的信号加入信号集中。sigdelset函数:用于将指定的信号从信号集中移除。sigismember函数:用于判断指定的信号是否包含在信号集中。

阻塞信号集(Blocked signal set):用于管理当前被阻塞的信号。当进程接收到一个被阻塞的信号时,该信号不会被处理,而是被加入到阻塞信号集中,直到该信号被解除阻塞后才会被处理。可以使用sigprocmask函数来设置和获取阻塞信号集。
未决信号集(Pending signal set):用于管理当前未被处理的信号。当进程接收到一个信号时,该信号会被加入到未决信号集中,直到该信号被处理后才会从未决信号集中删除。可以使用sigpending函数来获取当前未决信号集中的信号。
可以使用以下函数来操作阻塞信号集:
sigprocmask函数:用于设置和获取阻塞信号集。int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
```
其中,how参数指定操作类型,可以是以下三个值之一:
- SIG_BLOCK:将set指定的信号集中的信号添加到当前进程的阻塞信号集中。
- SIG_UNBLOCK:将set指定的信号集中的信号从当前进程的阻塞信号集中移除。
- SIG_SETMASK:用set指定的信号集替换当前进程的阻塞信号集。
set参数为指向要设置的信号集的指针,oldset参数为指向用于存储原阻塞信号集的指针。如果oldset参数不为NULL,则会将原阻塞信号集存储到该指针指向的位置中。sigpending函数:用于获取当前未决信号集中的信号。
#include <signal.h>
int sigpending(sigset_t *set);
其中,set参数为指向要存储未决信号集的指针。该函数将当前未决信号集存储到set指向的位置中。
需要注意的是,阻塞信号集和未决信号集可以用于控制信号的处理方式,但在使用时需要特别小心,以免导致信号丢失或不被处理。此外,对于一些特殊的信号,例如SIGKILL和SIGSTOP,无法被阻塞或捕获。

二十四、sigprocmask 函数使用

sigprocmask函数的原型如下:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);其中,how参数指定操作类型,可以是以下三个值之一:
SIG_BLOCK:将set指定的信号集中的信号添加到当前进程的阻塞信号集中。
SIG_UNBLOCK:将set指定的信号集中的信号从当前进程的阻塞信号集中移除。
SIG_SETMASK:用set指定的信号集替换当前进程的阻塞信号集。
set参数为指向要设置的信号集的指针,oldset参数为指向用于存储原阻塞信号集的指针。如果oldset参数不为NULL,则会将原阻塞信号集存储到该指针指向的位置中。需要注意的是,阻塞信号集是对当前进程全局生效的,因此需要谨慎使用,避免对整个进程的信号处理产生不良影响。同时,在阻塞信号集中阻塞某个信号时,该信号不会被丢弃,而是会被缓存起来,直到解除阻塞后再被处理。因此,如果在某个时刻多个信号被阻塞,这些信号都将被缓存起来,可能会导致后续处理时出现延迟。
二十五、sigaction 信号捕捉函数

sigaction函数的原型如下:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);其中,signum参数为要设置的信号编号,act参数为指向要设置的信号处理方式的结构体指针,oldact参数为指向用于存储原信号处理方式的结构体指针。如果oldact参数不为NULL,则会将原信号处理方式存储到该指针指向的位置中。需要注意的是,信号处理函数应该尽可能的简洁和快速,避免在信号处理函数中执行复杂的操作。同时,在信号处理函数中只能调用一些异步信号安全的函数,不能调用一些可能会阻塞的函数,否则可能会引起不可预知的问题。

2.内核检查进程的信号屏蔽字。在将信号发送给进程之前,内核会检查进程的信号屏蔽字,如果该信号在屏蔽字中,则该信号将被屏蔽,并不会发送给进程。否则,内核将继续发送信号。
3.内核发送信号给进程。如果信号没有被屏蔽,则内核会将信号发送给进程。在发送信号之前,内核会暂停进程的执行,并将进程的上下文保存到内核数据结构中,以便在信号处理函数执行完毕后恢复进程的执行。
4.内核执行信号处理函数。当进程接收到信号时,内核会执行该进程的信号处理函数。内核会先检查信号处理函数是否存在,如果存在,则将进程的上下文切换到信号处理函数中执行。在执行信号处理函数期间,内核会将进程的信号屏蔽字设置为信号处理函数中指定的信号屏蔽字,以避免其他信号的干扰。如果信号处理函数执行完毕,则将进程的上下文恢复,并继续执行进程。
二十六、SIGCHLD 信号

SIGCHLD信号的默认处理方式是忽略该信号。因此,如果父进程不捕捉该信号,当子进程终止或停止时,该信号不会对父进程产生任何影响。
可以使用sigaction函数捕捉SIGCHLD信号,并在信号处理函数中处理子进程的状态。当捕捉到SIGCHLD信号时,父进程可以调用waitpid函数等待子进程状态的改变,以获取子进程的退出状态码和其他信息。需要注意的是,当一个进程的子进程终止或停止时,内核只会向其父进程发送一次SIGCHLD信号,即使有多个子进程同时终止或停止。因此,在SIGCHLD信号处理函数中,需要使用循环调用waitpid函数来处理所有的子进程状态。另外,由于SIGCHLD信号是异步发生的,因此在信号处理函数中需要注意使用异步信号安全的函数,避免出现竞态条件等问题。
二十七、共享内存

高性能:共享内存的访问速度比其他进程间通信方式(如管道、消息队列等)更快,因为共享内存避免了数据的复制和系统调用的开销。
灵活性:共享内存可以在不同进程之间共享大量数据,不仅可以用于进程间通信,还可以用于在同一进程的不同线程之间共享数据。
易用性:使用共享内存的方式简单,只需要创建共享内存区域、连接到共享内存区域并访问共享内存区域即可。
可扩展性:共享内存可以动态地增加或减少内存大小,使得应用程序可以根据需要调整内存大小。
稳定性:共享内存是在内存中分配的,不像文件或管道等通信方式会受到硬盘容量或磁盘速度等因素的影响。因此,共享内存通常更加稳定和可靠。

创建共享内存区域。可以使用shmget系统调用创建一个共享内存区域,该调用会返回一个共享内存标识符。需要指定创建的共享内存大小、权限等参数。
连接到共享内存区域。可以使用shmat系统调用将进程连接到共享内存区域,该调用会返回共享内存区域的起始地址。需要指定共享内存标识符和连接方式等参数。
使用共享内存。连接到共享内存区域后,可以像访问普通内存一样访问共享内存区域。多个进程可以共享同一块内存区域,可以通过在内存中存储数据来进行进程间通信。
分离共享内存区域。可以使用shmdt系统调用将进程与共享内存区域分离,该调用会释放进程与共享内存区域的连接。需要指定共享内存区域的起始地址。
删除共享内存区域。可以使用shmctl系统调用删除共享内存区域,该调用会释放共享内存区域并撤销与该区域的所有连接。需要指定共享内存标识符和删除方式等参数。

-m:显示共享内存的信息;
-q:显示消息队列的信息;
-s:显示信号量的信息。ipcrm是Linux系统中的一个命令,用于删除IPC资源,包括消息队列、共享内存和信号量等。ipcrm命令的使用方法如下:-m:删除共享内存;
-q:删除消息队列;
-s:删除信号量。

二十八、进程组、会话、进程终端

物理终端:物理终端是指通过连接计算机的串口、并口、绘图仪和控制台等物理设备来提供终端功能,用户通过这些设备与计算机进行交互。在现代计算机中,物理终端已经较少使用。
虚拟终端:虚拟终端是指通过软件模拟的终端,提供与物理终端相同的终端功能。在Linux系统中,可以通过多个虚拟终端同时进行工作,每个虚拟终端都有自己的命令行提示符和命令执行环境,用户可以在不同的虚拟终端中运行不同的程序和命令。
在Linux系统中,可以使用以下命令来管理终端:
tty命令:用于显示当前终端的名称。
who命令:用于显示当前登录到系统的用户信息,包括用户名、登录时间、登录终端等。
login命令:用于登录到系统,需要输入正确的用户名和密码。
logout或exit命令:用于退出当前登录的终端。
Ctrl+Alt+F1F6键:用于切换不同的虚拟终端,F1F6键分别对应不同的虚拟终端。
reset命令:用于清屏并重置终端。
stty命令:用于设置终端属性,如终端大小、回显方式、流控制等。

ps命令:用于显示当前系统中的进程信息,可以使用ps命令查看每个进程的进程ID(PID)和进程组ID(PGID)等信息。
jobs命令:用于显示当前作业的状态,包括作业号、进程ID、作业状态等。
fg命令:用于将一个后台作业移动到前台运行。
bg命令:用于将一个前台作业移动到后台运行。
kill命令:用于向一个或多个进程发送信号,可以使用kill命令向进程组发送信号。
setpgid函数:用于设置进程的进程组ID。
getpgid函数:用于获取进程的进程组ID。

会话的主要作用是为了将一组相互关联的进程组织在一起,使它们能够共享同一个控制终端,并且可以进行作业控制和进程组间通信等操作。
在Linux系统中,可以使用以下命令来管理会话:
login命令:用于登录到系统,并创建一个新的会话。
tty命令:用于显示当前终端的名称,也可以用于查看当前会话的名称。
ps命令:用于显示当前系统中的进程信息,可以使用ps命令查看每个进程的会话ID(SID)等信息。
setsid命令:用于创建一个新的会话。
nohup命令:用于在后台运行一个进程,并且不受终端关闭等影响。
需要注意的是,在使用会话时需要谨慎操作,避免误操作导致系统数据丢失或系统崩溃等问题。在进行敏感操作时,建议使用root用户或具有足够权限的用户进行操作。

一个进程可以属于一个进程组,一个进程组可以有多个进程。
一个进程组可以属于一个会话,一个会话可以有多个进程组。
一个会话与一个控制终端相互关联,一个控制终端可以与多个会话关联。
一个会话中的第一个进程被称为控制进程,控制进程与控制终端相互关联。
控制进程可以通过控制终端与用户进行交互,同时可以对进程组进行作业控制和进程组间通信等操作。

setpgid(pid, pgid):将进程pid加入进程组pgid中,如果pgid为0,则将pid所在的进程组ID设置为pid本身的进程ID。
getpgid(pid):获取进程pid所在的进程组ID。
setsid():创建一个新的会话,并使调用进程成为该会话的首进程和进程组组长。
getpgrp():获取当前进程所在的进程组ID。
tcgetpgrp(fd):获取与文件描述符fd关联的终端的进程组ID。
tcsetpgrp(fd, pgrp):将与文件描述符fd关联的终端的进程组ID设置为pgrp。
需要注意的是,这些函数需要在合适的上下文中使用,否则可能会导致意想不到的结果或者错误。例如,在使用setsid函数创建一个新的会话之前,需要先关闭所有的文件描述符,否则会话可能会继承该进程打开的文件描述符,导致意想不到的结果。
另外,Linux系统也提供了一些命令来进行进程组、会话操作,例如:
jobs:查看当前shell会话中的作业信息。
fg:将一个后台作业移动到前台运行。
bg:将一个前台作业移动到后台运行。
kill:向一个或多个进程发送信号,可以使用kill命令向进程组发送信号。
二十九、守护进程

守护进程通常在系统启动时自动启动,以root用户身份运行,并且在运行过程中不受用户登录和注销的影响。它们通常不会向控制台输出信息,而是将日志信息写入日志文件中。

创建一个新的进程,然后通过调用setsid函数创建一个新的会话,并使该进程成为会话的首进程。
将该进程的umask设置为0,以便它可以创建任何文件和目录。
关闭所有的文件描述符,以避免它们继承自父进程。
将标准输入、标准输出和标准错误输出重定向到/dev/null或日志文件中,防止输出到控制终端。
在守护进程的主循环中执行需要执行的任务。