Linux学习总结

本文详细介绍了Linux操作系统,从Linux与Windows的区别、基础命令到高级应用,包括Vim使用、C程序的编译调试、文件系统管理、进程管理、内存管理和进程复制。通过实例演示了常用命令如ls、cd、mkdir、rm、vim、gcc、makefile等,并讲解了进程状态、文件操作、内存管理的概念。此外,还涵盖了静态库、动态库、文件压缩与解压、进程管理命令如ps、kill等,以及Linux基础理论,如main函数参数、并发与并行、printf缓冲区等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、Linux介绍

Linux: 操作系统 和 Windows 操作系统区别:

Linux终端

二、Linux系统目录结构

 三、基础命令

1.ls命令 查看路径下所有文件

2.cd命令:切换路径

3.clear 命令

4.pwd 命令

5.mkdir 命令 创建目录文件

 6.rmdir 命令 删除目录文件(空目录)

7.rm 删除文件命令

8.touch 命令 创建普通文件

9.文件类型

​编辑

10.修改文件权限 chmod

11.文件编辑命令

12.man 命令 查看帮助手册

13. cp 命令 拷贝

14.mv 命令

​编辑

 15.wc 命令

 16.more 命令 less 命令

17.head 命令

 18.tail命令

19.cat 命令

三、vim使用

vim命令介绍

命令模式

末行模式

四、Linux系统上C程序的编译与调试

1.程序的编译链接过程

2.编译链接过程

3. gcc分布编译链接

五、Linux平台C程序的编译链接

1.自定义头文件使用

2.gdb调试

六、makefile文件和进程管理命令

一、makefile文件

 二、文件压缩与解压命令

 1.find命令

 2.grep命令

 3.关机重启

4.tar 文件压缩与解压

三、进程管理命令

1.ps

2.kill

3.& 后台运行程序

4. runlevel 查看系统运行级别

七、静态库和动态库

1.静态库

2. 动态库

八、Linux基础理论

1.main主程序的三个参数

 2.如何执行程序

3.并发与并行

4.printf隐藏的缓冲区

九、内存管理与进程复制

一、计算机基本组成

二、进程概述

十、Linux进程复制

fork:进程复制

写时拷贝

十一、僵尸进程和文件系统调用

僵尸进程

孤儿进程

十二、文件操作和进程替换

操作文件的系统调用

将a.txt拷贝给b.txt

文件描述符 + fork

先open再fork,共享偏移量

先fork再open,不共享偏移量



一、Linux介绍

Linux: 操作系统 Windows 操作系统区别:

1 Linux 命令行操作系统 Windows 图形化界面
2 Linux 开源项目 (免费获取 Linux 操作系统的源码)。 内核源码,免费
Windows 不开源项目,收费
3 Linux :服务器端开发,手机和嵌入式设备(智能家具,遥控器 ....
Windows : 个人电脑
4 Linux: 多用户系统 Windows : 单用户系统
如何查看内核版本?
版本号: 5-> 主版本号,  15- 次版本号  0-> 修正版本号  52-> 修正版本第几次微调
稳定 -> 取决于修正版本号 奇数 不稳定 偶数 稳定版本

Linux终端

 右击,打开终端选项。

快捷键:ctrl shift + 三键组合 -> 放大字体

               ctrl - 二键组合 -> 缩小字体

$:普通用户身份

#:管理员身份

这里进入管理员身份使用的命令为sudo su。

二、Linux系统目录结构

  Linux系统的目录结构是一颗倒状数。

/bin : 存放命令
/etc: 配置文件
/home: 普通用户的家目录 (普通用户活动范围,具有权限)
/root : 管理员 家目录
/proc: 虚拟文件系统目录,以进程为单位存储内存映射关系。(进程:程序代码 + 运行起来)
/dev: 设备文件
/mnt : 临时挂载点
/lib : 库文件
/boot: 系统内核和启动所需要的文件
/tmp: 临时文件
/var : 系统日志 存放随时修改的一些文件。
/usr : 存放系统应用程序及文档。

 

 三、基础命令

tab键:补全文件信息

1.ls命令 查看路径下所有文件

ls 查看当前路径下所存在的文件。( Linux 一切皆文件)
ls -l 查看当前路径下所存在的文件 详细信息。 权限问题,创建日期,大小 ....
ls -a 显示该路径下所有文件(包含隐藏文件)
ls + 路径 查看指定路径下的文件。

2.cd命令:切换路径

cd + 路径
路径:绝对路径/ 相对路径
cd ~ 切换到普通用户的家目录里
cd /home/stu 切换到普通用户的家目录 跟上述相同
cd .. 切换到上一级目录
cd . 切换到当前路径

3.clear 命令

清屏命令

4.pwd 命令

查看当前路径

5.mkdir 命令 创建目录文件

mkdir 目录名 在当前路径下创建目录文件

 

 6.rmdir 命令 删除目录文件(空目录)

7.rm 删除文件命令

 rm -r 强制删除文件(目录是否是空都可以)

8.touch 命令 创建普通文件

可以指定一次创建多个文件

*模糊匹配:

rm *.c -> 删除当前目录下的以 .c 结尾的所有普通文件
rm * -> 删除当前目录下的所有普通文件
指定路径下删除,创建, ls 访问。
创建文件,删除 批量删除 * touch a.c b.c c.c
文件:不以后缀名区分文件类型。 -> Linux
main.c 文件 -> c 编译器要求 编译 c 程序 找文件必须见到 .c
windows: 以后缀名区分文件类型

9.文件类型

 

u:表示文件属主的访问权限

g:表示文件同组用户的访问权限

o:表示其他用户的访问权限

 r:可读 值:4

w:可写 值:2

x:可执行 值:1

- :无权限 值:0

10.修改文件权限 chmod

1.数字设定法

  采用数字设定法时,权限通常由三位数字组成,,每一位数字代表一种角色的权限。每个角色的数值由其所具有的权限对应的数值之和构成。 r:4.w:2 x:1

2.文字设定法

u表属主g同组人o其他人a所有人“+”表示增加权限“”表示去掉权限

 

11.文件编辑命令

 三种模式介绍

命令模式、插入模式(编辑模式)、末行模式

a:进入到当前光标后开始编辑
A:进入到当前光标所在行的行末开始编辑i1/进入当前光标位置开始编辑
l:进入当前光标所在行的行头开始编辑o/进入当前光标下一行开始编辑
o:进入当前光标上一行开始编辑

s:删除当前光标位置数据,在光标位置插入

在命令模式中,"/"表示文件内容从上向下查找数据,"?"默认从文件的下向上查找。

12.man 命令 查看帮助手册

man 数字 内容

1 -> 命令

2 -> 系统调用函数

3 -> 库函数

 如下:

13. cp 命令 拷贝

拷贝main.c到当前路径

 拷贝main.c到上一级目录下,并重命名为newmain.c

 

14.mv 命令

1.重命名 相同路径

mv 文件 文件 相同路径

mv 路径1/文件 路径1/文件

 将main.c重命名为aa.c。

2.文件移动(剪切)

mv 路径+文件 路径  解析为移动(剪切)

将当前路径下的main.c移动到上一级目录

 15.wc 命令

统计单词个数

 16.more 命令 less 命令

more 一页一页查看文件

less 文本内容查看器,查看文件内容,但是文件内容不会显示到界面上

17.head 命令

展示前n行文件内容

 18.tail命令

展示后n行文件内容

19.cat 命令

1.重定向

2.追加

 

 ctrl + d 结束输入

3.合并文件

 4.cat和tail合用

 

 终端1 cat >> main.c 重定向

终端2 tail -f main.c 实时追踪文件内容信息

tail -f 一般用于跟踪日志文件,实施编写展示。

三、vim使用

vim命令介绍

命令模式

ndd :从光标位置开始删除连续的n行内容

u :撤销上次操作

nyy :拷贝连续的n行内容(复制)

p:粘贴

r :替换字符按键r然后字符

G:光标移动到文件的末尾

gg ∶光标移动到文件开头

shitf+4:光标跳转到当前行尾

shift+6:光标跳转到当前行的行头

nG:光标跳转到第n行

末行模式

:wq保存退出 :w只保存 :q只退出不保存 :q!强制退出

/字符串从光标位置开始向下循环一圈查找该字符串的位置

?字符串从光标位置开始向上循环一圈查找该字符串的位置

n,m s/字符串1/字符串2/g

从n~m行之间将字符串1替换为字符串2

: set nu设置行号: set nonu取消行号

四、Linux系统上C程序的编译与调试

1.程序的编译链接过程

什么是可执行文件?

在Windows操作系统中,扩展名为.exe,.bat等的文件是可执行文件,可执行文件由指令和数据构成。Linux是靠文件属性来判断是否可执行。文件权限 x为可执行权限。

2.编译链接过程

3. gcc分布编译链接

1.预编译:gcc -E main.c -o main.i

2.编译:gcc -E main.i -o main.s

3.汇编:gcc -E main.s -o main.o

4.链接:gcc -E main.o -o main

一步编译:gcc -o main main.c

执行可执行程序:

路径+文件名  路径(相对 绝对均可)

./main           /home/stu/main

 打开z.i。

 打开z.s。

 

五、Linux平台C程序的编译链接

1.自定义头文件使用

vim main.c

vim add.h

 在一步编译时我们会遇到一个问题

 stdio.h 来源于 /usr/include 而add.h是用户自定义.h文件,故找不到该文件或目录。解决的方法可以将其移动到include中。

 

完成操作。

 在main.c中,  #include<add.h> 需要把当前路径下/usr/include/ 可以使用<add.h>。

2.gdb调试

vim test.c

 编译完成之后,进行运行

 这里我们发现,输入“end”之后程序仍然不会停止,这里使用ctrl+c中止程序。那么问题出现在哪?

我们进行调试

第一步,通过gcc -o main main.c -g

第二步,启动调试:gdb

 第三步,(gcb)提示符出现,可以输入命令

输入“l”查看代码

l 行号 查看当前行号前后10行代码内容

 b 行号 在行号位置下断点

info break 或者 info b

 第四步,运行代码(与逆行代码道断点位置阻塞)

run/r  运行代码

n/next 下一步

 

 p 变量 实时查看当前变量的信息

 

 当输入数据为“end”时,我们可以看到,实际上获取的时“end\n”,而"end\n"不等于“end”,所以即使输入“end”也不会中止。

第五步:调试结束 quit:结束调试

六、makefile文件和进程管理命令

一、makefile文件

我们需要写一个makefile文件,去实现自动化编译,而文件名称必须是makefile,是通过touch创建的普通文件。

1.前提是已经安装好make命令(make命令只针对makefile文件。make执行,默认直接调用makefile文件)

2.编辑C源文件 add.c mul.c main.c

3.编辑makefile文件

4.终端:执行make命令

 5.终端执行:make clean命令

 二、文件压缩与解压命令

 1.find命令

find + 路径 -name 文件名

 2.grep命令

grep "int" main.c 过滤出main.c中包含"int"字符的所有行进行输出

一般 | 管道和grep搭配使用

 3.关机重启

showdown  now 立刻关机

showdown -r now  重启

4.tar 文件压缩与解压

以main.c add.c mul.c 为例

压缩分步:先将三个文件打包,再压缩

解压分步:将压缩包解压再解包

c:创建包文件

f:指定目标为文件而不是设备

v:显示详细过程

t:显示包中的内容而不释放

x:释放包中的内容

z:使得tar有压缩和解压的功能

打包操作:tar cvf file.tar main.c add.c mul.c

将三个文件打包为file.tar

 压缩:gzip file.tar 生成file.tar.gz

 分布解压:gzip -d file.tar.gz ->file.tar

解包:tar xvf file.tar

一步压缩和解压命令

一步压缩:tar zcvf file.tar.gz main.c add.c mul.c

一步解压:tar zxvf file.tar.gz.0

三、进程管理命令

1.ps

默认显示与当前终端有关的进程信息

-e 显示系统中所有的进程信息

-f 显示更多的进程属性信息(全格式)

-L 展示当前终端上进程信息,线程LWP信息

 ps -ef 展示系统上所有进程的详细信息

 UID:执行该进程的用户ID

PID:进程号,一个进程唯一的号码对应

PPID:Parent 父进程的进程号

C:CPU使用率

STIME 进程启动时间

TTY :终端是哪个

?:若进程运行,与终端无关显示?

pst/0:由网络连接主机

tty1~tty6:本机

 TIME:运行的时间

CMD:进程启动时使用的命令

2.kill

kill 进程PID 结束当前进程

kill pid 结束当前进程

kill -9 pid 强制结束

kill -STOP pid 挂起进程

bg %任务号 进程/挂起程序,调到后台执行

fg % 任务号 将后台进程,调到前台执行

3.& 后台运行程序

启动程序:./main -> 路径+可执行程序 默认执行前台

./main & -> 后台运行进程

4. runlevel 查看系统运行级别

 0 关机;1 单用户级别;2 多用户无网络级别;3 多用户文本界面;4 无定义、自定义界面; 5 图形化界面;6 重启。

七、静态库和动态库

  库文件是计算机上的一类文件,提供给使用者一些开箱即用的变量、函数或类。库文件分为静态库和动态库,静态库和动态库的区别体现在程序的链接阶段。

  一般来说, Windows的静态库文件扩展名是.lib,动态库文件扩展名.是:.dll(Dynamic-Link Libraries),: Linux的静态库扩展名是a;动态库扩展名是.so(Shared Object)。内容一样,都是将函数封装在一起编译后供自己或他人调用。好处在于编译后的库文件看不到源代码,可保密。

  库是一组预先编译好的方法的集合。 Linux系统存储的库的位置—般在/lib 和/usr/lb在64位的系统上有些库也可能被存储在/usr/lib64下。库的头文件一般会被存储在/usr/include下或其子目录下。

  Linux库有两种,一种是静态库,其命令规则为libxxx.a,一种是共享库,其命今规则为libxxx.so

1.静态库

1.add.c mul.c 编译 add.o mul.o  gcc-c add.c mul.c

2.创建静态库 ar crv libfoo.a a.o mul.o

3.使用静态库

 

 -L :指定路径

-l:指定库名称(除开头lib和.a结尾)

查看静态库大小

静态库链接特点:每一个程序,静态链接库文件,生成的可执行文件都有一份副本。

2. 动态库

1.add.c mul.c 编译 add.o mul.o

2. add.o mul.o生成共享库文件

3.共享库的使用

第一种使用方法:

动态库放/lib或者/usr/lib路径下

 

 第二种使用方法:

修改环境变量

修改环境变量,使得动态库链接路径由原来的/usr/lib或/lib修改为自定义路径

命令:export LD_LIBRARY_PATH=.

删除环境变量 unset LD_LIBRARY_PATH

八、Linux基础理论

1.main主程序的三个参数

 

添加环境变量:export LD_LIBRARY_PATH=.

 

查看变量的值:echo $L_LIBRARY_PATH

清除环境变量: unset LD_LIBRARY_PATH

 2.如何执行程序

用户->计算机硬件->shell->内核->fwrite()->内核函数write(系统调用函数)

shell是用户和linux内核交互的接口程序

shell是终端

在提示符输入命令,经过shell先命令的解释后传递内核

shell通过$PATH寻找可执行程序,若找到可执行程序,被分解为系统调用并传递给内核执行。

3.并发与并行

并行:在同一个时刻,能够同时执行多个进程,每核,CPU在每一时刻执行一个进程,所以要同时进行多个进程的运行,多核CPU。

并发:在某一时间段,需要处理多个任务(进程,单核CPU,在某一时刻只能处理一个任务,多个进程通过进程切换,进程执行。

串行处理:多个任务,单核CPU,一个进程全部处理完成接下来处理下一个进程,等待该进程处理完,再进行下一个进程。

4.printf隐藏的缓冲区

windows无缓存 linux有缓存

\n 行缓冲

刷新缓冲区:1.程序结束前;2.碰见\n;3.碰见fflush(stdout),刷新缓冲区;4.缓冲区存放满

return 关键字,当前功能的结束

exit 函数调用,进程的退出

_exit 内核级别函数:exit函数内部实现结束,调用_exit进程中止。只结束程序,不刷新缓冲区。

九、内存管理与进程复制

一、计算机基本组成

 CPU包括控制器和运算器

运算器:也叫算数逻辑单元,完成对数据的各种常规运算,如加减乘除,也包括逻辑运算,移位,比较等。

控制器:它是整个计算机系统的控制中心,它只会计算机各部分协调的工作,保证计算机按照规定的目标和步骤有条不紊地进行操作及处理。

I/O设备就是输入设备和输出设备

系统总线:连接计算机各部件之间的一束公共信息线,它是计算机中传送信息代码的公共途径。

分为地址总线,数据总线,控制总线

计算机工作过程

用户打开程序

系统把程序代码段和数据段送入计算机的内存

控制器从存储器中取指令

控制器分析,执行指令,并为下一条指令做准备。

二、进程概述

1.进程:一个正在运行的程序。进程=程序+数据+PCB

2.PCB:进程控制块,是进程存在的唯一标志。用来描述进程的属性信息。OS是根据PCB来对并发执行的程序进行控制和管理的。

问:一个进程唯一有其标识PCB,一个程序能够跑起来,是现有PCB的产生还是先有进程的产生?

答:PCB

问:程序启动起来,程序终止时,先消失PCB还是进程实体?

答:进程实体。

什么是进程?

  操作系统中进程的所有操作都是通过运行相应的程序来实现,当运行某个程序时,就要将其从硬盘调入内存中,以供CPU进行运算和处理,这些系统正在运行的程序就成为进程。

   程序只占磁盘空间,不占用系统运行资源。进程由程序产生,进程要占用CPU和内存等系统资源,当关闭进程之后,他所占用的资源也随之释放。

  进程是操作系统资源分配和调度的基本单位。Linux是一个多用户多任务的操作系统,多用户是指多个用户可以在同一时间使用同一个linux系统,多任务是指linux中可以同时运行多个程序,执行多个任务,所有的进程都需要CPU进行运算和处理,而CPU在同一时刻只能处理一个进程数据。

进程状态:就绪、运行、阻塞

 CPU调度只会从就绪队列中取PCB(进程控制块)。

三、内存

虚拟内存提供的三个重要的能力:

1.它将主存堪称是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,根据需要在磁盘和主存之间来回传送数据,是的能够以运行比内存大的多的进程

2.它为每个进程提供了一致的地址空间,从而简化了存储器管理

3.它保护每个进程的地址空间不被其他进程破坏

物理地址和逻辑地址

物理地址:物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。在前端总线上传输的内存地址都是物理内存地址,编号从0开始一直到可用物理内存的最高端。这些数字被北桥(Nortbridge chip)映射到实际的内存条上。物理地址是明确的、最终用在总线上的编号,不必转换,不必分页,也没有特权级检查(notranslation, no paging, no privilege checks)。

逻辑地址:CPU所生成的地址。逻辑地址是内部和编程使用的、并不唯一。例如,进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于当前进程数据段的地址(偏移地址),不和绝对物理地址相干。

分段和分页技术

1.页表

  虚拟地址转换为物理地址,要有页表。

  页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。

分级页表:一个32位(2^32)逻辑地址空间的计算机系统,页大小为4KB(2^12),那么页表有一百万条目(2^(32-12))。假设每个条目占4B,则需要4MB物理地址空间来存储页表本身。利用多级页表,可以减少页表所占用的空间。

  一个32位逻辑地址空间的计算机系统,页大小为4KB,那么页表有一百万条目。假设每个条目占4B,则需要4MB物理地址空间来存储页表本身。利用多级页表,可以减少页表所占用的空间。

逻辑页和物理页

十、Linux进程复制

fork:进程复制

 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main(){
  5     int n = 0;
  6     char *s = NULL;
  7     pid_t pid = fork(); //返回值: 当前父进程 返回>0 子进程 返回值 == 0
  8     if(pid == 0){//子进程
  9         n = 3;
 10         s = "child";
 11     }
 12     else{
 13         n = 7;
 14        s = "parent"; //父进程
 15     }
 16    for(int i=0;i<n;i++){
 17       printf("pid=%d,ppid=%d,s=%s,&n=%p\n",getpid(),getppid(),s,&n);//getpid() 获取当前进程的pid getppid()当前进程的父进程
 18   sleep(1);
 19    }
 20 }
                                                                                                                                                                                                                                                                                                                                                      
pid=2766,ppid=2283,s=parent,&n=0x7fff6d436694
pid=2767,ppid=2766,s=child,&n=0x7fff6d436694
pid=2766,ppid=2283,s=parent,&n=0x7fff6d436694
pid=2767,ppid=2766,s=child,&n=0x7fff6d436694
pid=2766,ppid=2283,s=parent,&n=0x7fff6d436694
pid=2767,ppid=2766,s=child,&n=0x7fff6d436694
pid=2766,ppid=2283,s=parent,&n=0x7fff6d436694
pid=2766,ppid=2283,s=parent,&n=0x7fff6d436694
pid=2766,ppid=2283,s=parent,&n=0x7fff6d436694
pid=2766,ppid=2283,s=parent,&n=0x7fff6d436694

问:子进程通过fork进程复制父进程得到的,那么,子进程n与父进程n是不是同一块物理内存,逻辑内存?

如上代码,&n的值,不论是父进程还是子进程的值是一样的。物理地址不同。

写时拷贝

  写时拷贝是一种可以推迟甚至免除拷贝数据的技术,内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。

问:不修改n的值,父子进程n的物理内存是否相同?

答:相同,此时父子进程共享n数据,也就是写时拷贝。

关于fork的一些问题:

1.下面代码,经过fork()之后,打印几个“A”?

 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main()
  5 {
  6     printf("A\n");
  7     fork();
  8 }

答:1个A,fork()之后没有代码,子进程结束,父进程也结束。

2.

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main()
  5 {
  6     fork();
  7     printf("A\n");
  8 }

答:2个A,一个是父进程打印出的A,另一个是子进程打印的A。

3.

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main()
  5 {
  6     printf("A");
  7     fork();
  8 }

答:2个A,A会先进入缓冲区,直到程序结束,释放缓冲区中的数据,父子进程各一个A。

4.

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main()
  5 {
  6     for(int i=0;i<2;i++){
  7         fork();
  8     printf("A\n");
  9     }
 10 }

答:6个

5.

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main()
  5 {
  6     for(int i=0;i<2;i++){
  7         fork();
  8     printf("A");
  9     }
 10 }

答:8个,需要考虑缓冲区。

十一、僵尸进程和文件系统调用

 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main(){
  5     int n = 0;
  6     char *s = NULL;
  7     pid_t pid = fork(); //返回值: 当前父进程 返回>0 子进程 返回值 == 0
  8     if(pid == 0){//子进程
  9         n = 3;
 10         s = "child";
 11     }
 12     else{
 13         n = 7;
 14        s = "parent"; //父进程
 15     }
 16    for(int i=0;i<n;i++){
 17       printf("pid=%d,ppid=%d,s=%s,&n=%p\n",getpid(),getppid(),s,&n);//getpid() 获取当前进程的pid getppid()当前进程的父进程
 18   sleep(1);
 19    }
 20 }
        
sen@sen-virtual-machine:~/test1012$ ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3161 pts/0    00:00:00 main
   3162 pts/0    00:00:00 main
   3164 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ pid=3161,ppid=2283,s=parent,&n=0x7ffda9884284
pid=3162,ppid=3161,s=child,&n=0x7ffda9884284
ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3161 pts/0    00:00:00 main
   3162 pts/0    00:00:00 main
   3165 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ pid=3161,ppid=2283,s=parent,&n=0x7ffda9884284
ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3161 pts/0    00:00:00 main
   3162 pts/0    00:00:00 main <defunct>
   3166 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ ppid=3161,ppid=2283,s=parent,&n=0x7ffda9884284
s
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3161 pts/0    00:00:00 main  //父进程
   3162 pts/0    00:00:00 main <defunct> //子进程(僵死)
   3167 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ pspid=3161,ppid=2283,s=parent,&n=0x7ffda9884284

    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3161 pts/0    00:00:00 main
   3162 pts/0    00:00:00 main <defunct>
   3168 pts/0    00:00:00 ps

僵尸进程

  子进程先于父进程结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。

子进程的退出,父进程未获取子进程的退出码。

解决僵尸问题:wait(),子进程运行结束,父进程把子进程的退出码获取之后就可以解决。

  1 #include<stdio.h>
  2 #include<sys/wait.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 int main(){
  6     int n = 0;
  7     char *s = NULL;
  8     pid_t pid = fork(); //返回值: 当前父进程 返回>0 子进程 返回值 == 0
  9     if(pid == 0){//子进程
 10         n = 3;
 11         s = "child";
 12     }
 13     else{
 14         n = 7;
 15        s = "parent"; //父进程
 16        int val; // 4字节,获取子进程的退出码
 17        pid_t child_pid = wait(&val);//子进程结束,wait阻塞,直到子进程运行结束,获取子进程的退出码之后,继续当前父进程。
 18        if(WIFEXITED(val)){
 19              printf("wait的返回值%d    退出码:%d\n",child_pid,WEXITSTATUS(val));
 20                }
 21     }
 22    for(int i=0;i<n;i++){
 23       printf("pid=%d,ppid=%d,s=%s,&n=%p\n",getpid(),getppid(),s,&n);//getpid() 获取当前进程的pid getppid()当前进程的父进程
 24   sleep(1);
 25    }
 26 }
      

pid=3237,ppid=3236,s=child,&n=0x7ffd9edf8f8c
pid=3237,ppid=3236,s=child,&n=0x7ffd9edf8f8c
pid=3237,ppid=3236,s=child,&n=0x7ffd9edf8f8c
wait的返回值3237    退出码:0
pid=3236,ppid=2283,s=parent,&n=0x7ffd9edf8f8c
pid=3236,ppid=2283,s=parent,&n=0x7ffd9edf8f8c
pid=3236,ppid=2283,s=parent,&n=0x7ffd9edf8f8c
pid=3236,ppid=2283,s=parent,&n=0x7ffd9edf8f8c
pid=3236,ppid=2283,s=parent,&n=0x7ffd9edf8f8c
pid=3236,ppid=2283,s=parent,&n=0x7ffd9edf8f8c
pid=3236,ppid=2283,s=parent,&n=0x7ffd9edf8f8c

将父进程与子进程的n调换

ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3185 pts/0    00:00:00 main
   3186 pts/0    00:00:00 main
   3187 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ pid=3186,ppid=3185,s=child,&n=0x7fff3cffdd34
pid=3185,ppid=2283,s=parent,&n=0x7fff3cffdd34
ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3185 pts/0    00:00:00 main
   3186 pts/0    00:00:00 main
   3188 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ pid=3185,ppid=2283,s=parent,&n=0x7fff3cffdd34
pid=3186,ppid=3185,s=child,&n=0x7fff3cffdd34
ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3185 pts/0    00:00:00 main
   3186 pts/0    00:00:00 main
   3189 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ pid=3186,ppid=3185,s=child,&n=0x7fff3cffdd34
pspid=3186,ppid=1660,s=child,&n=0x7fff3cffdd34

    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3186 pts/0    00:00:00 main
   3190 pts/0    00:00:00 ps
[1]+  已完成               ./main
sen@sen-virtual-machine:~/test1012$ pid=3186,ppid=1660,s=child,&n=0x7fff3cffdd34
ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3186 pts/0    00:00:00 main
   3191 pts/0    00:00:00 ps
sen@sen-virtual-machine:~/test1012$ pid=3186,ppid=1660,s=child,&n=0x7fff3cffdd34
ps
    PID TTY          TIME CMD
   2283 pts/0    00:00:00 bash
   3186 pts/0    00:00:00 main
   3192 pts/0    00:00:00 ps

孤儿进程

  父进程先于子进程结束,子进程的父进程由init进程接管子进程。

文件操作:fopen fread fwrite fclose

  1 #include<fcntl.h>
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 #include<unistd.h>
  5 int main(){
  6     int fd = open("./a.txt",O_WRONLY|O_CREAT,0600);//打开a.txt 以读写模式打开文件,不存在,则创建文件+权限,再以读写模式打开
  7     //fd 文件描述符 fd 对应一个文件 文件关闭后,文件描述符归还操作系统  fd>=0
  8     if(fd == -1){
  9         printf("open_err\n");
 10         exit(1);
 11     }
 12     close(fd);
 13     //open("./a.txt",O_WRONLY); //不存在文件 不会创建 直接返回失败
 14     exit(0);
 15 }
       

十二、文件操作和进程替换

操作文件的系统调用

int open(const char* pathname, int flags);//用于打开一个已存在的文件
int open(const char* pathname,int flags,mode_t mode);//用于新建一个文件,并设置访问权限

参数介绍:

pathname:将要打开的文件路径和名称
flags:文件打开方式
O_RDONLY——只读方式打开
O_WRONLY——只写方式打开
O_RDWR——读写方式打开
O_CREAT——如果文件不存在则创建文件
O_APPEND——文件末尾追加
O_TRUNC——清空文件,重新写入
mode:权限,例如“0600”,创建文件的权限不仅受mode的影响,也受到用户掩码umask的掩码
返回值:文件描述符
 

ssize_t read(int fd, void *buf, size_t count);

参数介绍:

fd:对应的文件描述符

buf:从文件中读出数据到 buf 中

count:计划一次从文件中读多少字节数据

返回值:实际读到的字节数

ssize_t write(int fd, const void *buf, size_t count);

参数介绍:

fd:对应的文件描述符

buf:从 buf 中向文件写入数据

count:计划一次向文件中写多少字节数据

int close(int fd);

参数介绍:

fd:要关闭的文件描述符

可读:

  1 #include<fcntl.h>
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 #include<unistd.h>
  5 int main(){
  6     int fd = open("a.txt",O_RDONLY|O_CREAT,0600);//打开a.txt 以读写模式打开文件,不存在,则创建文件+权限,再以读写模式打开
  7     //fd 文件描述符 fd 对应一个文件 文件关闭后,文件描述符归还操作系统  fd>=0
  8     if(fd == -1){
  9         exit(1);
 10     }
 11     char buff[128] = {0};
 12     int num = read(fd,buff,128);
 13     printf("num=%d,buff=%s\n",num,buff);
 14     close(fd);
 15     exit(0);
 16 }
num=6,buff=abcde

可写:

1 #include<fcntl.h>
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 #include<unistd.h>
  5 int main(){
  6     int fd = open("a.txt",O_WRONLY|O_CREAT,0600);//打开a.txt 以读写模式打开文件,不存在,则创建文件+权限,再以读写模式打开
  7     //fd 文件描述符 fd 对应一个文件 文件关闭后,文件描述符归还操作系统  fd>=0
  8     if(fd == -1){
  9         exit(1);
 10     }
 11     //char buff[128] = {0};
 12     //int num = read(fd,buff,128);
 13     //printf("num=%d,buff=%s\n",num,buff);
 14     int num = write(fd,"hello",5);
 15     printf("num=%d\n",num);
 16     close(fd);
 17     exit(0);
 18 }
  
num=5

将a.txt拷贝给b.txt

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<fcntl.h>
  5 #include<assert.h>
  6 int main(){
  7     //a.txt拷贝给b.txt
  8     char* s_name = "a.txt";
  9     char* new_name = "b.txt";
 10     int fdr = open(s_name,O_RDONLY);
 11     assert(fdr!=-1);
 12     int fdw = open(new_name,O_WRONLY|O_CREAT,0600);
 13     assert(fdw!=-1);
 14     //依次读a.txt 写入b.txt
 15     char buff[128] = {0};
 16     int num = 0;
 17     while((num=read(fdr,buff,128))>0){
 18     write(fdw,buff,num);
 19     }
 20     close(fdr);
 21     close(fdw);
 22     exit(0);
 23 }
       

文件描述符 + fork

先open再fork,共享偏移量

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<fcntl.h>
  5 #include<assert.h>
  6 int main(){
  7     int fd = open("a.txt",O_RDONLY);
  8     assert(fd!=-1);
  9     pid_t pid = fork();
 10     assert(pid!=-1);
 11     if(pid == 0){
 12         char buff[10] = {0};
 13         read(fd,buff,1);
 14         printf("son:buff=%s\n",buff);
 15         sleep(1);
 16         read(fd,buff,1);
 17         printf("son:buff=%s\n",buff);
 18 
 19     }
 20     else{
 21     char buff[10] = {0};
 22     read(fd,buff,1);
 23     printf("father:buff=%s\n",buff);
 24     sleep(1);
 25     read(fd,buff,1);
 26     printf("father:buff=%s\n",buff);
 27     }
 28     close(fd);
 29     exit(0);
 30 }
           

运行结果:

father:buff=a
son:buff=b
father:buff=c
son:buff=d

先fork再open,不共享偏移量

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<fcntl.h>
  5 #include<assert.h>
  6 int main(){
  7     pid_t pid = fork();
  8     assert(pid!=-1);
  9     int fd = open("a.txt",O_RDONLY);
 10     assert(fd!=-1);
 11     if(pid == 0){
 12         char buff[10] = {0};
 13         read(fd,buff,1);
 14         printf("son:buff=%s\n",buff);
 15         sleep(1);
 16         read(fd,buff,1);
 17         printf("son:buff=%s\n",buff);
 18 
 19     }
 20     else{
 21     char buff[10] = {0};
 22     read(fd,buff,1);
 23     printf("father:buff=%s\n",buff);
 24     sleep(1);
 25     read(fd,buff,1);
 26     printf("father:buff=%s\n",buff);
 27     }
 28     close(fd);
 29     exit(0);
 30 }
     
father:buff=a
son:buff=a
son:buff=b
father:buff=b

子进程先将父进程的PCB复制一份,分为两个进程,同时执行fork之后的程序,父子进程同样进行open

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值