Linux高级编程
2025-07-24 15:24:10 0 举报
AI智能生成
快速了解Linux知识, 抓住关键点
作者其他创作
大纲/内容
进程间通信(IPC)
IPC
广义上一切能是进程间相互交流的对象和方法都是IPC,比如文件、管道、socket等
狭义上的IPC特指消息队列】信号量和共享内存三种对象
应用范围
消息队列应用于不同进程之间少量数据的顺序共享
消息量应用于进程间的同步互斥的控制
共享内存应用于进程间大批数据的随机共享访问
条件
必须通过内核
机制
进程1从用户空间拷到内核缓冲区,进程2从内核缓冲区读走
目的
数据传输
共享数据
一个进程改变,所有进程改变
通知事件
一个进程向一个或一组进程发消息通知其发生什么事件(如:进程终止需要通知父进程)
资源共享
涉及到锁、同步机制
进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程)此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
分类
早期UNIX进程间通信
管道
特点
单向
先进先出
环形队列
无结构
固定大小
简单的流控制体制
分类
无名管道
特性
半双工通信方式,具有固定的读写端
具有亲缘关系的进程
基于文件描述符的通信方式
当创建管道时,会创建两个文件描述符
fds[0],固定用于读管道, fds[1]固定用于写管道
当创建管道时,会创建两个文件描述符
fds[0],固定用于读管道, fds[1]固定用于写管道
#include <unistd.h>
int pipe(int filedes[2]);
限制
1.两个进程通过一个管道只能实现单项通信,比如父写子读,如调换需要另开一个管道
2.管道读写端通过打开的文件描述符来传递,所以两进程要通信必须从公共祖先继承文件描述符
fork函数其大作用
特殊情况
1.写端关闭,读完数据再读返回0
2.写端未关闭,但不写数据,读完数据后,再次read就会阻塞
3.读端关闭,再写,进程通常异常终止
4.管道数据满时,写动作会阻塞
标准流管道
基于文件流的模式
popen();
FILE *popen ( char *command, char *type);
type是r与w二者之一,以参数type中第一个字符代表的方式打开
pclose();
int pclose( FILE *stream );
返回系统调用wait4( )的状态,破化管道
命名管道(FIFO)
概念
可以用于两个互不相关进程实现通信,,可以通过路径名哎指出,并且文件系统中可见
两进程可以将FIFO当作文件进行读写
遵循先进先出规则,读从开始处返回,写将数据添加到末尾
特性
在文件系统中作为特殊的设备文件
不同祖先的进程可以通过管道共享数据
可保存于文件系统中
#include <sys/types.h>
#include <sys/state.h>
#include <sys/state.h>
int mkfifo(const char *filename,mode_t mode);
FIFO
信号
概念
软件中断
在软件层次上对硬件中断的一种模拟
处理异步事件
特性
信号可直接进行用户空间进程与系统内核进程的交互,也可以内核进程通知用户空间进程发生了什么事件,可以任何时候发送,无需知道进程所处状态。
如果该进程未处于执行态,就由内核保存起来,直至其运行再传递
如果该进程处于阻塞,传递就延迟,直到其阻塞取消再传递
如果该进程处于阻塞,传递就延迟,直到其阻塞取消再传递
产生
通过终端按键产生信号
SIGINT(Ctrl-C)默认动作终止进程
SIGQUIT(Ctrl-\)默认动作是终止进程并且Core Dump
Ctrl-Z产生SIGTSTP信号,挂起符,内核给所有前台进程组发送该信号
SIGQUIT(Ctrl-\)默认动作是终止进程并且Core Dump
Ctrl-Z产生SIGTSTP信号,挂起符,内核给所有前台进程组发送该信号
调用系统函数想进程发信号
kill -SIGEGV pid
#include<signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
#include<signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
kill -l 查看系统支持的信号列表
raise 函数允许进程香向自身发送信号
由软件条件产生信号
alarm.c
alarm函数发送的信号SIGALARM默认的系统动作为终止该进程,因此在程序调用pause之后,程序终止
alarm();
int pause(void);
通常用于判断信号是否已到
向读端已关闭的管道写数据的产生SIGPIPE信号
硬件异常产生信号
硬件检测到并通知内核,然后内核发送适当的信号
例如除以0,CPU运算单元会产生异常,
内核将这个异常解释为SIGFPE
内核将这个异常解释为SIGFPE
主要来源
异常
进程运行过程中出现异常
其他进程
一个进程可以向另一个或一组进程发送信号
终端中断
作业控制
前台、后台进程的管理
分配额
CPU超时或文件大小突破限制
通知
通知j进程某事件发生,如I/O就绪等
报警
计时器到期
关联动作
忽略此信号
SIGKILL和SIGTOP永远不能被忽略
忽略硬件异常,结果无法想象
忽略硬件异常,结果无法想象
执行默认动作
异常终止
在进程当前目录下,把进程的地址空间内容、寄存器内容保存到Core文件中,然后终止程序
退出
直接终止
忽略
停止
挂起
继续
如果该进程被挂起,就恢复其运行,否则,忽略信号
提供一个信号处理函数
要求内核在处理该信号时切换到用户态执行这个处理函数,即捕捉一个信号
内核中的
表现形式
表现形式
状态
递达
未决
信号集sigset_t存储
阻塞
信号集sigset_t存储,阻塞信号集也叫信号屏蔽字
每个信号都有两个标志位分别表示阻塞和未决(0 / 1),还有一个函数指针表示处理动作
linux中有64个信号
前32常规信号,它在传递之前产生多次只计一次
后32实时信号,它会排队等候
前32常规信号,它在传递之前产生多次只计一次
后32实时信号,它会排队等候
信号集操作函数
#include<signal.h>
int sigemptyser(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
sigprocmask
int sigprocmask(int how, const sigset_t *set,
sigset_t *oset);
sigset_t *oset);
sigpending
int sigpending(sigset_t *set);
信号处理
主要方式(2种)
使用简单的signal函数
使用时只需把要处理的信号和处理函数列出就ok
主要是对前32种非实时信号的处理,不支持信号传递信息
使用信号集函数
内核实现信号捕捉机制
1.用户注册信号处理函数;
2.当前发生中断或异常,从main函数切换到内核态;
3.在中断处理完毕后要返回main时先检查是否有信号抵达
4.有信号,内核返回用户态时并不是恢复main函数上下文,而是执行sighandler函数,它与main使用不同堆栈空间,不存在调用和被调用的关系,是两个独立的控制流程
5.sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态
6.如果没有新的信号递达,就返回用户态回复main函数上下文继续执行
#include<signal.h>下的常用信号出来函数
signal
void (*signal(int signum, void (*handler)(int)))(int);
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
sigaction
int sigaction(int signo, const struct sigaction *act, struct
sigaction *oact);
sigaction *oact);
清除僵尸进程
父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,
这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,
也不会通知父进程。
这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,
也不会通知父进程。
异步程序
信号处理函数
是单独的控制流程,与主控制流程是异步的,二者不存在调用和被调用关系,使用不同的堆栈空间
引入后使得一个进程具有多个控制流程,
如果这些控制流程访问相同的全局资源(全局变量、硬件资源等),就有可能出现冲突
如果这些控制流程访问相同的全局资源(全局变量、硬件资源等),就有可能出现冲突
可/不可重入函数
例如链表的insert函数被不同的控制流程调用,有可能再第一次调用还没返回时就再次进入该函数,成为重入
insert函数访问一个全局链表,有可能因为重入而造成错误,成为不可重入函数
条件(符合其一就为不可重入)
调用了malloc或free,因为malloc也是全局链表来管理堆的。
调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
如果一个函数只访问自己的局部变量和资源,称为可重入函数
竞态条件
系统运行的时序并不像我们写程序时设想的那样。
由于异步事件再任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,有可能由于时序问题而导致错误,这叫做竞态条件
由于异步事件再任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,有可能由于时序问题而导致错误,这叫做竞态条件
sigsuspend
解决竞态条件问题
包含了pause的挂起等待功能,再对时序要求严格的场合下都应该调用sigsuspend而不是pause
#include <signal.h>
◼ int sigsuspend(const sigset_t *sigmask);
◼ int sigsuspend(const sigset_t *sigmask);
基于System V进程间通信
System V消息队列
定位
用来在进程之间传递消息
特点
似于管道,优于管道
消息队列先进先出
将输出的信息进行了打包处理,保证以每个消息消息为单位进行接收
可以对货物进行分类服务,根据类别出货
是一个链表
允许一个或多个进程向它写消息,一个或多个进程从中读消息
具有一定的FIFO特性,但可实现消息的随即查询
消息存在于内核中,由队列ID来标识
具有一定的FIFO特性,但可实现消息的随即查询
消息存在于内核中,由队列ID来标识
实现
创建和打开队列
int msgget (key_t key, int flag)
添加消息
读取消息
控制消息队列
System V信号灯
System V共享内存
基于Socket进程间通信
POSIX进程间通信
posix消息队列、 posix信号灯、 posix共享内存
使用较多的通信方式
共享内存
同一内存空间,牵一发动全身
信号量
作为进程间以及同一进程不同线程间同步手段
套接字
可用于不同机器之间的进程通信
初识操作系统
名称
Operating System
角色
魔术师角色/管理者角色
定义
是管理计算机系统的全部硬件资源包括软件资源及数据资源;控制程序运行;改善人机界面;为其它应用软件提供支持等,使计算机系统所有资源最大限度地发挥作用,为用户提供方便的、有效的、友善的服务界面。
关系
操作系统为用户程序提供了一个虚拟机器界面,而应用程序
运行在这个界面之上。
运行在这个界面之上。
文件I/O
系统调用
定义
操作系统提供给用户程序的一组“特殊”接口
用户通过这组接口来获得内核提供的特殊服务
用户通过这组接口来获得内核提供的特殊服务
实例
用户可以通过进程控制相关的系统调用
来创建进程、实现进程调度、进程管理等
来创建进程、实现进程调度、进程管理等
意义
为了更好的保护内核空间,程序运行空间分为内核空间和用户空间
用户进程通常情况不允许访问内核数据,也无法使用内核函数,
只能操作用用用户空间
只能操作用用用户空间
场景
用户需要获得一定的系统服务
系统规定用户进程进入内核空间的具位置
系统规定用户进程进入内核空间的具位置
API
用户编程接口
系统命令(可执行程序)高于API,实际内部引用了API来实现相应功能
方式
通过软中断向内核提交请求
以获得内核服务的接口
以获得内核服务的接口
操作
错误
errno
strerror();
perror
文件
一切皆文件
1.普通文件
2.目录文件
3.链接文件
4.设备文件
文件描述符
<unistd.h>
<unistd.h>
非负整数,索引值,指向内核进程打开文件的记录表
可作为参数传给相应函数
文件描述符的范围是0 ~ OPEN_MAX
Linux 下OPEN_MAX为1048576
Linux 下OPEN_MAX为1048576
进程启动所打开的3个文件(描述符)
标准输入0/STDIN_FILENO
标准输出 1/STDOUT_FILENO
标准出错处理 2/STDERR_FILENO
I/O
关于STDOUT_FILENO和标准I/O
区别
1、数据类型不一致
stdin等类型为 FILE *
STDIN_FILENO等类型为 int
使用stdin的函数主要有:fread、fwrite、fclose等,基本上都以f开头
使用STDIN_FILENO的函数有:read、write、close等
stdin等类型为 FILE *
STDIN_FILENO等类型为 int
使用stdin的函数主要有:fread、fwrite、fclose等,基本上都以f开头
使用STDIN_FILENO的函数有:read、write、close等
2、层次不一致
stdin等属于标准I/O,高级的输入输出函数。在<stdio.h>。
STDIN_FILENO等是文件描述符,是非负整数,一般定义为0, 1, 2,直接调用系统调用,在<unistd.h>。
stdin等属于标准库处理的输入流,其声明为 FILE 型的,对应的函数为标准库调用等
STDIN_FILENO等属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数为系统级调用。
stdin等属于标准I/O,高级的输入输出函数。在<stdio.h>。
STDIN_FILENO等是文件描述符,是非负整数,一般定义为0, 1, 2,直接调用系统调用,在<unistd.h>。
stdin等属于标准库处理的输入流,其声明为 FILE 型的,对应的函数为标准库调用等
STDIN_FILENO等属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数为系统级调用。
关系
对于stdin等可以使用fileno()函数(用来取得参数stream指定的文件流所使用的文件描述符)来取得该文件流对应的文件描述符。
fileno(stdin) = STDIN_FILENO = 0
fileno(stdout) = STDOUT_FILENO = 1
fileno(stderr) = STDERR_FILENO = 2
fileno(stdin) = STDIN_FILENO = 0
fileno(stdout) = STDOUT_FILENO = 1
fileno(stderr) = STDERR_FILENO = 2
open()
打开或创建文件,可指定文件属性,用户权限等参数
open.c
creat()
创建一个新文件
close()
关闭一个文件,当一进程终止时,所有打开的文件都由内核自动关闭
read()
将指定的文件描述符中读出的数据放到缓冲区,并返回实际读入的字节数
实际读到的字节数少于要求读到的字节数
1.文件已到末尾
2.从终端读,一次一行
3.从网络读,网络中缓冲机制
可能造成返回值小于要求字节数
2.从终端读,一次一行
3.从网络读,网络中缓冲机制
可能造成返回值小于要求字节数
write()
向打开的文件写数据,写操作从文件当前指针位置开始
lssek()
在指定的文件描述符中将文件指针定位到相应的位置
lseek.c
堵塞与非堵塞
堵塞
非堵塞
fcntl()
通过fcntl设置的都是当前进程如何设置访问设备和文件的
访问控制属性,如读、写、追加、非堵塞、加锁等
访问控制属性,如读、写、追加、非堵塞、加锁等
不设置文件或设备本身的属性,例如文件的读写权限、
串口波特率等。
串口波特率等。
五大功能
用fcntl改变FileStatusFlag.c
ioctl()
特点
ioctl 函数是I/O操作的杂物箱。不能用本章中其他函数表示的I / O操作
通常都能用ioctl表示。
通常都能用ioctl表示。
终端I/O是ioctl 的最大使用方面(POSIX.1已经用新的函数代替ioctl进
行终端I / O操作)。
行终端I / O操作)。
ioctl更多的是用于设备控制,比如磁盘的格式化,MODEM设备,磁带
的快进、快倒等。
的快进、快倒等。
ioctl甚至可以读写一些数据,但是这些数据是不能用read、write读写的,
称为out-of-band数据。
称为out-of-band数据。
read\write读写的是in-band数据,是I/O操作的主体,而ioctl命令传送的
是控制信息,其中的数据是辅助的数据。
是控制信息,其中的数据是辅助的数据。
ioctl.c
对终端窗口大小数据据读取
mmap()
磁盘文件映射到内存空间地址
mmap.c
truncate()
文件截断
sync()
文件同步,把文件直接写入磁盘
不建议使用,因为影响系统性能(加塞)
文件和目录操作函数
exercise
将两个文件中数字相加格式相同
0 条评论
下一页