将近日学习Linux基础编程,系统编程,网络编程的word笔记搬运过来,图片无法直接粘贴,就丢失掉了。
日后工作学习中使用与扩充维护还是word笔记
系统编程day01
内容简介:
程序和进程
磁盘中的二进制文件,运行的程序,cpu,虚拟地址空间,物理内存
并行和并发
时间碎片和多核
pcb进程控制块
文件描述符,进程id
进程三态
就绪态,执行态,挂起态
虚拟地址空间
4G32位,内核区,用户区,堆栈全局代码区共享库区
fopen打开的*FILE是什么
文件描述符表,文件指针,缓存(库函数比系统函数快)
进程控制
fork,pid_t, getpid,getppid, 读时共享写时复制, 循环创建多个线程
进程相关命令
psaux, ps ajx, kill -9 pid
exec函数族
execl,execlp, pathname, 占位符, NULL哨兵
进程回收
孤儿进程僵尸进程,wait,waitpid(0, -1, -pid, pid), int stat, 父进程死僵尸也死(init)
1 了解进程相关的概念
程序和进程
程序:二进制文件,占用磁盘空间
进程:启动的程序
所有数据都在内存中
需要占用更多的系统资源
cpu,物理内存
2 并行和并发
并发:
是时间段的概念,不是时间点的概念
cpu每分钟一杯咖啡,让200个人每人都喝一口咖啡
cpu在处理进程时在不同的进程间轮询地切换
并行:
cpu多核,有多个处理器
3 pcb进程控制块(结构体)每启动一个进程
structtask_struct
进程id,系统中每个进程都有唯一的id,c语言中用pid_t类型表示,就是非负整数
进程三态
进程有初始态,就绪,运行,挂起,终止等状态
进程切换时需要保存和恢复的cpu寄存器
虚拟地址空间
Linux下每启动一个进程,就对应一个虚拟地址空间,里面有运行程序的所有信息,但是运算都在物理内存中。
描述控制终端的信息。打印都是打印到终端上
当前工作目录(pwd输出当前工作目录)
umask掩码
文件描述符表(最大1024)
和信号相关的信息(linux独有,windows是事件驱动的)
用户id和组id
会话(session,多个进程组)和进程组(子进程和父进程默认在同一个组里)
进程可以使用的资源上限ulimit–a 文件描述符个数上限等
fopen打开的是什么
fopen建立了一个内存缓存区,将磁盘文件先存入内存
进程控制
进程的虚拟地址空间
fork函数创建子进程
fork函数的返回值
pid==0说明是子进程(儿子什么都不知道)
pid>0说明是父进程(爸爸知道儿子的名字)
子进程创建成功后代码的执行位置
从父进程fork后面的代码位置开始执行,因为代码段也是copy的父进程。
父子进程的执行顺序
不一定,谁抢到了cpu谁先继续运行,先执行的不一定先打印
如何区分父子进程
通过fork的返回值pid_t pid
getpid/getppid获取进程ID
getpid得到当前进程的PID
getppid得到当前进程父进程的PID
父子进程的全局变量
fork之后父子进程的数据是一样的
读是共享,写是复制。一旦该全局变量被修改,那么就会在物理内存中copy出一个新的全局变量来被修改。但是打印他们的地址却是一样的,这是因为父子进程的地址空间都是从0开始的,地址即使同为1048,那也是在不同进程的不同地址空间中的。(不同大街的相同门牌号)
./a.out fork了一个子进程,但是shell不知道a.out创建了子进程,shell检测到父进程执行完毕就切换到了前台,此时子进程有可能还没有执行完毕,还在向终端打印。此时输入shell命令,shell就会去解析它。
循环创建多个进程
循环创建进程时,if(pid == 0) break; 不让子进程创建子进程。
通过循环因子i判断是第几个进程
for( i = 0 ; i<5; ++i)
{
pid= fork();
if(pid == 0 )
{ //是子进程
break;
}
}
if( i == 0)
{
puts(“firstprocess”);
}
打印的顺序不能代表程序执行的先后顺序
进程相关命令
shell命令ps
ps aux
TTY是进程依赖于终端的名字,如果是?就表示不依赖于终端
依赖于终端的进程一般是与用户交互。
ps aux | grep filename
|管道,是内核中的缓冲区
左边写的数据,写到管道中,然后grep命令会从管道中获取数据进程查询再输出到终端
ps ajx 查看的信息更全
可以看到父进程的pid
PGID是进程组
SID是会话ID
查找到还是没有查找到aaaaaaaaaaa?这是没查找到
管道的前后两个命令是两个进程在工作(找到的是grep命令本身)
kill发信号给某个进程
34--64是给系统预留的信号
1--31号信号,不同的操作系统是类似的
kill-9+pid:9号信号是SIGKILL,无条件杀死一个进程
kill -l命令可以查看信号
如果父子进程执行的是不相关的操作,那么代码写在一起,很多余
exec()函数族替换进程的代码段
作用
让父子进程执行不相干的操作
能够替换进程地址空间中的代码.txt段
当前程序中调用其他应用程序
首先想到exec()之前要先fork()
子进程调用exec()中的函数,那么子进程的代码就被挖走了
执行指定目录下的程序
int execl(constchar *path, const char *arg,…); 记住有个占位符
path是要执行的程序的路径(建议是绝对路径)
变参:arg要执行的程序需要的参数
第一个arg:占位的作用,写什么都行,一般是程序的名字
后边的arg:需要的参数
参数写完之后:NULL
一般执行自己写的程序
execl(“/bin/ls”,“这是占位符”, “-lah”, NULL); NULL是哨兵表示参数结束
int execlp(const char*file, const char *arg, … ); p是pass的意思
参数与execl()相同
执行系统自带的应用程序
/bin下
执行PATH环境变量中能够搜索到的程序
如何打印exec()函数的错误记录?通过返回值,如果是-1那么就调用失败。或者直接perror(),exec()失败后errno会被设置。然后退出进程exit(1);
exec()函数族是不需要判断返回值的,直接perror(),然后exit(1).因为函数执行成功那就不会执行下面的语句。
进程回收
孤儿进程
父进程生成子进程,父进程结束,子进程还活着,就叫孤儿进程
Linux系统的孤儿会被init进程领养init进程变成了孤儿进程的父亲
为了释放子进程占用的系统资源
子进程结束之后,会自己释放用户区所占用的内存空间
但是无法释放pcb,pcb必须由父进程释放
僵尸进程
孩子死了,爹还活着,但是父进程没有释放子进程的pcb,子进程就变成了僵尸进程
僵尸进程不是活着的进程。所以父进程一定要等子进程都死了,自己才能死。
wait 阻塞函数pid_t wait(int *status);
头文件:sys/wait.hsys/types.h
条件:子进程死亡
返回值:
-1:回收失败,已经没有子进程了
>0:是回收的子进程对应的pid
参数:
判断子进程是如何死的status为变量即可,不需要是指针
正常退出 WIFEXITED(status) !=0 是有返回值的
WEXITSTATUS(status);获取进程退出状态参数exit(参数)/return返回值
被某个信号杀死了WIFSIGNALED(status) != 0 没有返回值,被信号杀死
WTERMSIG(status);获得使程序终止信号的编号
调用一次wait函数,只能回收一个子进程,回收多个子进程需要循环,如果返回值为-1,就结束循环。
waitpid可以设置阻塞非阻塞,可以选择回收哪个子进程 pid_t waitpid (pid_tpid, int *status, int options);
pid:
pid>0:回收某个子进程的pid
pid == -1:回收所有子进程
仍然需要循环回收,一次只能回收一个
while((wpid = waitpid(-1, &status, XXX) ) != -1 );
pid= 0:回收当前进程组的所有的子进程。已经送人的子进程无法回收。
pid<0:子进程的pid取负号,就是回收不在同一个进程组的子进程
option
0:waitpid阻塞
WNOHANG:非阻塞 wait no hang
返回值:
-1:回收失败,没有子进程
>0:被回收的子进程的pid
由于是非阻塞的,可能存在无法回收因为子进程还在运行
=0:子进程还在运行,回收失败
如何杀死僵尸进程
子进程在父进程结束之前结束,如果父进程不wait,那一定会成为僵尸进程吗?
子进程结束,父进程还在sleep,那么子进程变成僵尸
子进程结束,父进程从sleep中醒来,如果不wait,子进程还是僵尸
所以,父进程一定要wait子进程,除非父进程也很快就结束了
一般僵尸进程很难直接kill掉,不过您可以kill僵尸爸爸。父进程死后,僵尸进程成为”孤儿进程”,过继给1号进程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。
ps -e -o ppid,stat | grep Z | cut -d” ” -f2| xargs kill -9
kill -HUP `ps -A -ostat,ppid | grep -e’^[Zz]‘ | awk ’{print $2}’`
我将nova-novncproxy stop后再start,僵尸进程即消失,问题解决。
另外子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。就是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,而此时,尽管对它的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
在终端中打开多个终端ctrl+shift+t
打开多个终端:ctrl+alt+t
段错误:内核使用11号信号杀死段错误应用程序
1访问了非法内存
2在不可写的空间进行写操作
3栈溢出
系统编程day02
学习目标:
1 熟练使用pipe进行父子进程间的通信
2 熟练使用pipe进行兄弟进程间的通信
pipe只能进行有血缘关系间进程通信
3 熟练使用fifo进行无血缘关系进程间通信(有名管道)
4 熟练掌握mmap函数的使用 (内存映射的方式,把磁盘文件加载到动态加载区)
5 掌握mmap创建匿名映射区的方法 (添加宏)
6 使用mmap进行有血缘关系的进程间通信
7 使用mmap进行无血缘关系的进程间通信
进程间通信相关概念
1 什么是IPC
进程间通信
InterProcessCommunication
2 进程间通信常用四种方式
1管道 – 简单
2信号 – 系统的,使用复杂,开销小
3共享映射区 – 有无血缘关系进程间通信都可以
4本地套接字 – 稳定
5普通文件等
pipe管道(匿名)
1 管道的概念
在磁盘上没有对应的文件,不占用磁盘空间
本质:
内核缓冲区
伪文件,不在磁盘上
特点:
两部分:
读端,写端对应两个文件描述符
数据写端流入,读端流出
操作管道的进程被销毁后,管道自动被释放了
管道两端默认是阻塞的,对文件描述符的操作,读写就是阻塞非阻塞,是文件描述符对应的文件的行为。
2 管道的原理
内部实现方式:队列
环形队列,队头和队尾是相对的
特点:先进先出
缓冲区大小:
默认4K
大小会根据实际情况适当调整
3 管道的局限性
队列:
数据只能读取一次,不能重复读取
半双工:(这里的半双工和通信上的还不太一样)
单工:遥控器-信号传输方向单向
半双工:对讲机-同一时刻数据只能沿一个方向传输
匿名管道:
适用于有血缘关系的进程
4 创建匿名管道int pipe(intfd[2]);
fd– 传出参数,内核创建了管道,需要返回两个文件描述符给你操作
fd[0]-读端
fd[1]-写端
成功返回0,否则返回-1
5 父子进程使用管道通信
fork()应该在管道创建之后,这样子进程的fd[]和父进程的fd[]里文件描述符相同
单个进程也可是使用管道完成读写操作
父子进程间通信是否需要sleep()?不需要,管道默认是阻塞的。
父子进程ps aux | grep ”bash“
思路:ps aux的数据,不输出到终端,输出到管道;grep从管道中读取数据
1pipe(fd)创建管道
2fork()创建子进程
3关闭不需要的管道端
1数据重定向:dup2(pipe_fd[0], STDIN_FILENO) ; STDIN_FILENO/STDOUT_FILENO
多个文件描述符指向同一个文件
2execlp使用ps
如果不关闭执行读操作的写端,每执行一次,grep操作就变多,因为grep在等待数据的写入,grep阻塞了。不用的管道端一定要关闭。
管道的读写行为
读操作
有数据
read(fd);- 正常读,返回读出的字节数
无数据
1写端全部关闭
read解除阻塞,返回0
相当于读文件读到了尾部
2没有全部关闭
read阻塞,直到写端有数据写入,所以不写,一定要关闭
写操作
读端全部关闭
管道破裂,进程被终止
内核给当前进程发信号SIGPIPE
读端没全部关闭
缓冲区满了
write阻塞
缓冲区没满
write继续写
2 查看管道缓冲区大小
命令
ulimit -a
函数
fpathconf,name是宏,表示不同的数据项,返回该数据项的大小
如何设置非阻塞
默认读写两端都阻塞
设置读端为非阻塞pipe(fd)
fcntl-变参函数获取修改文件状态
复制文件描述符-dup
修改文件属性-open的时候对应的flag属性
设置方法:
1获取原来的flags
int flags = fcntl(fd[0]), F_GETFL);
2设置新的flags
flag |= O_NONBLOCK
fcntl(fd[0], F_SETFL, flags);
fifo有名管道先进先出
1 特点
有名管道
在磁盘上有这样一个文件ls –l -> p
文件大小永远为0,也是一个伪文件
数据存在内核中,有对应的缓冲区
半双工的通信方式
2 使用场景
没有血缘关系的进程间通信
3 创建方式
命令:
mkfifo管道名
函数:
int mkfifo(const char *pathname, mode_tmode);
mode是权限,也会|~umask
4 fifo文件可以使用IO函数进行操作
5 进程间通信
fifo文件àmyfifo
两个不相干的进程A(a.c),B(b.c)
a.c-- >read
intfd = open(“myfifo”, O_RDONLY);
read(fd,buf, sizeof(buf) );
close(fd);
b.c -->write
intfd1 = open(“myfifo”, O_WDONLY);
write(fd1,“hello world”, 11);
close(fd1);
默认的是有阻塞属性的
通过命令行参数传参来实现创建指定名字的fifo文件
access判断文件是否存在
内存映射区
mmap创建内存映射
头文件:sys/mman.h
作用:将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件
函数原型:
void*mmap(
void addr, //映射区首地址,传NULL,内核知道
size_t length, //映射区的大小,4K整数倍
int prot, //映射区权限, PORT_READ, PORT_ERITE
//映射区必须有读权限
int flags, //标志位参数, MAP_SHARED, MAP_PRIVATE
//MAP_SHARED:修改的内存数据同步到磁盘
//MAP_PRIVATE:修改的内存数据不同步到磁盘
int fd, //文件描述符,映射谁,谁的fd
//通过open获得fd
off_t dffset //映射时文件指针的偏移量
//必须是4k的整数倍
);
返回值:
调用成功,返回映射区的首地址
调用失败,返回MAP_FAILED(void *) (-1)
size_t length映射区的大小实际上都是4k的整数倍,且不能为0,其实就是文件的大小
如何获得文件的大小?lseek函数
int port: PORT_READ
PORT_WRITE
mmap的释放 munmap
intmunmap(void *addr, size_t length);
addr– mmap的返回值,映射区的首地址
length:映射区的长度
返回值:
释放失败,返回-1