admin管理员组

文章数量:1598902

linux基础

  • Bash解析器常用快捷键
  • 命令类型查看方法
  • linux命令格式
  • man查找手册
  • 外建命令
  • 绝对路径和相对路径
  • 目录相关命令
    • pwd
    • cd
    • mkdir
    • rmdir
  • 文件类型
  • 通配符
  • 文件相关命令
  • 文件内容查看命令
  • 查找相关命令
    • find
    • grep
  • 管道
  • 压缩包管理
    • tar
    • gzip
    • tar与gzip结合使用
    • bzip2(类似于gzip)
    • zip和unzip
  • 文件权限管理
    • 访问权限说明
    • chmod
      • 字母法
      • 数字法(二进制)
  • linux下软件的安装和卸载
    • 在线安装
    • 安装包安装
  • 其他命令
    • tree
    • In
  • vim
    • 基本概念
    • 实用操作
      • 命令模式下的操作
      • 末行模式下的操作
  • gcc编译器
    • 编译命令格式
    • gcc常用选项
  • 静态库和动态库
    • 静态链接和动态链接
    • 静态库的制作和使用
    • 静态库的测试
    • 动态库的制作和使用
    • 动态库的测试
  • GDB调试器
    • GDB简介
    • 生成调试信息
    • 启动GDB
    • 显示源代码
    • 断点操作
    • 条件断点
    • 维护断点
    • 调试代码
    • 数据查看
    • 自动显示
    • 查看修改变量的值
  • Makefile
    • Makefile语法规则
    • Make命令格式
    • Makefile简单示例
    • Makefile中的变量
    • Makefile中的三种自动变量
    • 模式规则(模式匹配)
    • Makefile中的两个函数
    • Makefile中的伪目标
    • Makefile的工作原理
  • 系统调用简介和实现
    • 系统调用和库函数的区别
    • C库中IO函数工作流程
    • 错误处理函数
    • 虚拟地址空间
    • 文件描述符
    • 常用的文件操作函数
      • open函数
      • close函数
        • 案例
      • write函数
        • 案例
      • read函数
        • 案例
    • 阻塞和非阻塞
      • 简单案例
    • lseek函数
      • 案例分析
  • 文件目录操作函数
    • stat
      • 简单案例
      • stat显示文件类型
    • stat显示文件第二个版本
    • stat获取文件的权限
    • 其他操作函数
    • 文件描述符复制
      • dup函数
      • dup2函数
    • fcntl函数
      • 实现文件描述符复制
      • 实现文件状态标记获取和位置
  • 目录相关操作
    • 打开和关闭目录
    • 读目录

Bash解析器常用快捷键

  1. Tab键
    补齐命令
    补齐路径
    显示当前目录下的所有目录
  2. 清屏
    clear作用为清除终端上的显示(类似于DOS的cls清屏功能),也可使用快捷键:Ctrl + L ( “L” 为字母 )。
    deng@itcast:~$ clear
  3. 中断进程
    ctlr+c的作用是中断终端的操作。
    deng@itcast:/$ sleep 3000
  4. 遍历输入的历史命令
    从当前位置向上遍历:ctrl + p (↑)
    从当前位置向下遍历: ctrl + n(↓)
  5. 光标相关操作
    光标左移: ctrl + b (←)
    坐标右移: ctrl + f (→)
    移动到头部: ctrl + a(Home)
    移动到尾部: ctlr + e(End)
  6. 字符删除
    删除光标前边的字符:ctrl + h(Backspace)
    删除光标后边的字符:ctrl + d
    光标后边的字符即光标覆盖的字符
    删除光标前所有内容:ctrl + u
    删除光标后所有内容:ctrl + k

命令类型查看方法

使用type命令查看:
格式:
type [-afptP] 名称 [名称 …]​ 显示命令类型的信息。
使用方法示例:
deng@itcast:~$ type -a cd
cd 是 shell 内建
deng@itcast:~$ type -a echo
echo 是 shell 内建
echo 是 /bin/echo
deng@itcast:~$ type -a ls
ls 是 `ls --color=auto’ 的别名
ls 是 /bin/ls

linux命令格式

command [ -options] [parameter1] …
说明:
command:命令名,相应功能的英文单词或单词的缩写
[-options]:选项,可用来对命令进行控制,也可以省略,[]代表可选
parameter1 …:传给命令的参数,可以是零个一个或多个

man查找手册

man 是 Linux 提供的一个手册,包含了绝大部分的命令、函数使用说明。
该手册分成很多章节(section),使用 man 时可以指定不同的章节来浏览不同的内容。
man 中各个 section 意义如下:
1).Standard commands(标准命令)
2).System calls(系统调用,如open,write)
3).Library functions(库函数,如printf,fopen)
man 1 printf
man 2 read
man 3 printf
##】 内建命令
help
help pwd

外建命令

help
ls --help

绝对路径和相对路径

绝对路径 -----> /开头
相对路径

目录相关命令

pwd

pwd:打印当前工作目录的名字

cd

cd:cd命令可以帮助用户切换工作目录

mkdir

mkdir:新建目录(默认新建空目录)
mkdir -p a/b/c 在当前目录下创建a/b/c目录树

rmdir

rmdir:删除目录(空目录)
rmdir -p a/b/c 删除目录树
rmdir file{1…100}删除file1到file100的目录
rmdir "file 1"删除file 1目录

文件类型

                             开头

白色:普通文件 -
绿色:可执行文件
红色:压缩文件
蓝色:目录文件 d
青色:链接文件
黄色:设备文件
灰色:其他文件

字符设备 c
块设备 b
管道文件 p
符号链接 l
套接字 s

通配符

案例:
ls [123].html
1.html 2.html 3.html
ls [1-3].html
1.html 2.html 3.html

文件相关命令

ls:列出目录的内容(-l 以列表方式显示文件的详细信息)

touch:1)如果文件不存在, 创建新文件(只能是普通文件,不能是文件夹) 2)如果文件存在, 更新文件时间
touch file
touch file{1,2,3}
touch “file1”

cp:将给出的文件或目录复制到另一个文件或目录中
cp -r dir1/ dir2/ 把dir1目录复制到dir2目录下
cp file1 file2 dirs/ 把file1 file2文件拷贝到dirs目录下
cp add.c sub.c 系统会创建一个名为 sub.c 的新文件,其内容与 add.c 完全相同
cp -rf dir1/ dir2/ 是用于递归地将目录 dir1/ 及其内容复制到 dir2/

rm:可通过rm删除文件或目录(删除非空目录 rm -r /目录名)
rm -i a 删除文件a,需要人工确认
rm -f a 强制删除文件a
rm -r dirs 递归删除目录dirs,需要人工确认
rm -rf dirs 递归删除目录dirs,不需要人工确认

mv:移动文件或目录,也可以给文件或目录重命名
mv file1 file2 把file1命名为file2
mv file dirs/ 把file文件移动到dirs目录下

文件内容查看命令

cat:将文件内容一次性输出到终端。缺点:终端显示的内容有限,如果文件太长无法全部显示
cat /etc/passwd 查看/etc/passwd内容
cat 1.txt

less:将文件内容分页显示到终端,可以自由上下浏览
q退出

head:head命令从文件头部开始查看前 n 行的内容
head -n 30 /etc/passwd 查看/etc/passwd前30个字符

tail:从文件尾部向上查看最后 n 行的内容
tail -n 30 /etc/passwd 查看/etc/passwd后30个字符

查找相关命令

find

find:用来在特定的目录下搜索符合条件的文件,也可以用来搜索特定用户属主的文件
按文件名查询:使用参数 -name
命令:find + 路径 + -name +“文件名”

按文件大小查询:使用参数 -size
命令:find + 路径 + -size + 范围

按照文件类型查询:使用参数 -type
命令:find+路径±type+类型
注:查找指定目录下的普通文件:find /home -type f(用f不用-)

grep

grep:grep允许对文本文件进行模式查找。如果找到匹配模式, grep打印包含模式的所有行
在grep命令中输入字符串参数时,最好引号或双引号括起来。例如:grep ‘a ’1.txt

命令:grep -r + “查找的关键字” + 路径
搜索目录需要添加参数: -r
查找 /home/itcast 下包含“hello,world“字符串的文件:grep -r “hello,world” /home/itcast

管道

管道(|):一个命令的输出可以通过管道做为另一个命令的输入。
cat /etc/passwd | less cat /etc/passwd输出后,通过less可以进行浏览,此时的less就相当于命令的输入

压缩包管理

基本概念:
打包:把多个文件弄成一个文件
压缩:把文件从大变小

tar

默认不压缩
tar命令:可以把一系列文件归档到一个大文件中,也可以把档案文件解开以恢复数据(打包)
tar使用格式: tar [选项] 打包文件名 文件
参数 含义
-c 生成档案文件,创建打包文件
-v 列出归档解档的详细过程,显示进度
-f 指定档案文件名称,f后面一定是.tar文件,所以必须放选项最后
-t 列出档案中包含的文件
-x 解开档案文件

常用的三种搭配:
tar -cvf
tar -cvf test.tar 1.txt 2.txt 3.txt 把后面三个文件生成一个归档文件(创建归档文件test.tar不存在)

tar -xvf
tar -xvf test.tar 解除归档文件

tar -tvf
tar -tvf sysctl.tar 查询压缩包里面的文件(场景:当遇到大的压缩包的时候 如果里面的文件不需要则先用这个查看)

gzip

tar与gzip命令结合使用实现文件打包、压缩
tar只负责打包文件,但不压缩,用gzip压缩tar打包后的文件,其扩展名一般用xxxx.tar.gz
gzip不能直接压缩目录 如果是目录,需要先压缩为归档文件再进行压缩

gzip语法:gzip [选项] 被压缩文件
gzip a b c d e 压缩a b c d e文件
gzip -d a.gz a.gz被解压了,不保留原文件
gzip -r sysctl 到sysctl目录里面对所有文件压缩
gzip -d *.gz 批量解压 / gunzip *.gz

tar与gzip结合使用

方法1:
tar -cvf sysctl.tar sysctl/
gzip sysctl.tar
tar:用于创建 tar 归档文件的命令。
-c:创建一个新的归档文件。
-v:在处理文件时显示详细信息。
-f sysctl.tar:指定归档文件的名称(sysctl.tar)。
sysctl/:要打包的目录。
执行这两个就会出现sysctl.tar.gz
方法2:
tar [选项] [归档文件名] [要打包的文件或目录]
tar -czvf sysctl.tar.gz sysctl
tar -czvf /tmp/sysctl.tar.gz sysctl
如何对于刚刚所压缩的进行解压?
方法1:
gunzip sysctl.tar.gz
tar -xvf sysctl.tar
方法2:
tar -xzvf sysctl.tar.gz sysctl
解压到指定目录(加-C)
tar -xzvf sysctl.tar.gz -C /tmp

bzip2(类似于gzip)

tar -cjvf test.tar.bz2 test 生成一个bz2压缩包
tar -xjvf share.tar.bz2 -C /test 解压bz2压缩包到test目录
万能解压:tar -xvf

zip和unzip

通过zip压缩文件的目标文件不需要指定扩展名,默认扩展名为zip。
压缩文件:zip -r 目标文件(没有扩展名) 源文件
zip myzip b c d 把文件bcd打包压缩生成压缩包
zip -r t.zip test/ 压缩目录

解压文件:unzip -d 解压后目录文件 压缩文件
unzip -d test myzip.zip(test是目录)

文件权限管理

访问权限说明

读权限(r)
对文件而言,具有读取文件内容的权限;对目录来说,具有浏览目录的权限。
写权限(w)
对文件而言,具有新增、修改文件内容的权限;对目录来说,具有删除、移动目录内文件的权限。
可执行权限(x)
对文件而言,具有执行文件的权限;对目录了来说该用户具有进入目录的权限。
注意:通常,Unix/Linux系统只允许文件的属主(所有者)或超级用户改变文件的读写权限。

rwx 含义
r read 表示可读取,对于一个目录,如果没有r权限,那么就意味着不能通过ls查看这个目录内部的内容。
w write 表示可写入,对于一个目录,如果没有w权限,那么就意味着不能在目录下创建新的文件。
x excute 表示可执行,对于一个目录,如果没有x权限,那么就意味着不能通过cd进入这个目录。

chmod

chmod 修改文件权限有两种使用格式:字母法与数字法

字母法

字母法:chmod u/g/o/a +/-/= rwx 文件
[ u/g/o/a ] 含义
u user 表示该文件的所有者
g group 表示与该文件的所有者属于同一组( group )者,即用户组
o other 表示其他以外的人
a all 表示这三者皆是

[ ±= ] 含义

  • 增加权限
  • 撤销权限
    = 设定权限

chmod o+w file 给文件file的其它用户增加写权限:
chmod u-r file 给文件file的拥有者减去读的权限:
chmod g=x file设置文件file的所属组权限为可执行,同时去除读、写权限:
chmod u=rw,g=r,o=r file 连续操作用逗号

数字法(二进制)

rwx
111 —>7
rw-
110 —>6
r–
100 —>4
r-x
101 —>5

chmod 0754 file
chmod -R 777 test/ 目录被改了权限 同时 目录下的文件的权限也被改了

linux下软件的安装和卸载

在线安装

命令 含义
sudo apt update 获得最新的软件包的列表
sudo apt install xxx 从源中安装xxx软件
sudo apt remove xxx 删除xxx软件
sudo apt clean 清理安装包

安装包安装

在Ubuntu下安装文件为deb格式
软件安装
sudo dpkg -i xxx.deb
软件卸载
sudo dpkg -r 软件名

其他命令

tree

tree 以树状形式查看指定目录内容,使用该命令需要安装软件 tree

In

ln命令主要用于创建链接文件。Linux链接文件类似于Windows下的快捷方式

链接文件分为软链接和硬链接:
软链接(符号链接):软链接不占用磁盘空间,源文件删除则软链接失效。(符号链接的权限都有但不具有可执行的意义,符号链接本身不存储数据存储的是路径 /home 就是5)
硬链接:硬链接只能链接普通文件,不能链接目录。

语法:
硬链接:ln 源文件 链接文件
软链接:ln -s 源文件 链接文件

readlink命令
读取符号链接的链接内容(也就是目标文件的路径):readlink a_link2

vim

基本概念

vi有三种基本工作模式: 命令模式、文本输入模式(编辑模式)、末行模式。

打开文件
vim filename:打开或新建文件,并将光标置于第一行行首,如果文件不存在,则会新建文件。

编辑文件
如果通过vi打开一个已经存在的文件,首先进入命令模式,此时输入的任何字符都被视为命令,不能输入内容。
在命令模式输入i

保存文件
第一步:进入命令模式(ESC)
第二步:shifit + z z
一定要先退出插入模式(按Esc进入命令模式),然后(小写状态下),shift + zz (按住 “shift” + 按两下“z”键),或者(大写状态下:ZZ) 即可保存退出当前文件。

实用操作

命令模式下的操作

1)切换到编辑模式
按键 功能
i 光标位置当前处插入文字
I 光标所在行首插入文字
o(字母) 光标下一行插入文字(新行)
O(字母) 光标上一行插入文字(新行)
a 光标位置右边插入文字
A 光标所在行尾插入文字
s 删除光标后边的字符,从光标当前位置插入
S 删除光标所在当前行,从行首插入

  1. 光标移动
    按键 功能
    Ctrl + f 向前滚动一个屏幕
    Ctrl + b 向后滚动一个屏幕
    gg 到文件第一行行首
    G(大写) 到文件最后一行行首,G必须为大写
    mG或mgg 到指定行,m为目标行数
    0(数字) 光标移到到行首(第一个字符位置)
    $ 光标移到到行尾
    l(小写L) 向右移动光标
    h 向左移动光标
    k 向上移动光标
    j 向下移动光标
    ^ 光标移到到行首(第一个有效字符位置)

3)复制粘贴
按键 功能
[n]yy 复制从当前行开始的 n 行
p 把粘贴板上的内容插入到当前行

4)删除
按键 功能
[n]x 删除光标后 n 个字符
[n]X 删除光标前 n 个字符
D 删除光标所在开始到此行尾的字符
[n]dd 删除从当前行开始的 n 行(准确来讲,是剪切,剪切不粘贴即为删除)
dG 删除光标所在开始到文件尾的所有字符
dw 删除光标开始位置的字,包含光标所在字符
d0(0为数字) 删除光标前本行所有内容,不包含光标所在字符
dgg 删除光标所在开始到文件首行第一个字符开始的所有字符

5)撤销恢复
按键 功能
.(点) 执行上一次操作
u 撤销前一个命令
ctrl+r 反撤销
100 + . 执行上一次操作100次

6)保存退出
按键 功能
ZZ(shift+z+z) 保存退出

7)查找
按键 功能
/字符串 从当前光标位置向下查找(n,N查找内容切换)
?字符串 从当前光标位置向上查找(n,N查找内容切换)

8)替换
按键 功能
r 替换当前字符
R 替换当前行光标后的字符(ESC退出替换模式)

9)可视模式
按键 功能
v 按字符移动,选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制
Shift + v 行选(以行为单位)选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制
Ctrl + v 列选 选中文本,可配合h、j、k、l选择内容,使用d删除,使用y复制

末行模式下的操作

1)保存退出

按键 功能
:wq 保存退出
:x(小写) 保存退出
:w filename 保存到指定文件
:q 退出,如果文件修改但没有保存,会提示无法退出
:q! 退出,不保存
all 表示所有

2)替换
按键 功能
😒/abc/123/ 光标所在行的第一个abc替换为123
😒/abc/123/g 光标所在行的所有abc替换为123
:1,10s/abc/123/g 将第一行至第10行之间的abc全部替换成123
:%s/abc/123/g 当前文件的所有abc替换为123
:%s/abc/123/gc 同上,但是每次替换需要用户确认
:1,$s/abc/123/g 当前文件的所有abc替换为123

3)分屏
按键 功能
:sp 当前文件水平分屏
:vsp 当前文件垂直分屏
: sp 文件名 当前文件和另一个文件水平分屏
: vsp 文件名 当前文件和另一个文件垂直分屏
ctrl-w-w 在多个窗口切换光标
:wall/:wqall/:qall 保存/保存退出/退出所有分屏窗口
vim -O a.c b.c 垂直分屏
vim -o a.c b.c 水平分屏

  1. 其它用法(扩展)
    按键 功能
    :!man 3 printf 在vim中执行命令 (q退出)
    :r !ls -l 将ls -l执行的结果写入当前文件中
    :r /etc/passwd 将/etc/passwd文件中的内容写入到当前文件中
    :w /tmp/txt 将当前文件内容写入到/tmp/txt文件中
    :w! /tmp/txt 强制将当前文件内容写入到/tmp/txt文件中
    :1,10s/^g 将第1行到10行行首添加// (^表示行首) //\转移字符
    :1,10s#^#//#g 将第1行到10行行首添加// (#可以临时代替/ 分隔)
    :%s/;/\r{\r\treturn0;\r}\r/g 将;替换成{ return 0; }
    :1,10s#//##g 将第1行到10行行首去掉// (#可以临时代替/ 分隔)

  2. 配置文件
    局部配置文件(推荐)
    deng@itcast:~/share/2nd$ vim ~/.vimrc
    全局配置文件:
    deng@itcast:~/share/2nd$ sudo vim /etc/vim/vimrc

gcc编译器

编译命令格式

gcc [options] file…
g++ [options] file…
命令、选项和源文件之间使用空格分隔
一行命令中可以有零个、一个或多个选项
文件名可以包含文件的绝对路径,也可以使用相对路径
如果命令中不包含输出可执行文件的文件名,可执行文件的文件名会自动生成一个默认名,Linux平台为a.out,Windows平台为a.exe

gcc常用选项

选项 作用
-o file 指定生成的输出文件名为file
-E 只进行预处理
-S(大写) 只进行预处理和编译
-c(小写) 只进行预处理、编译和汇编
-v / --version 查看gcc版本号
-g 包含调试信息
-On n=0~3 编译优化,n越大优化得越多
-Wall 提示更多警告信息
显示所有的警告信息
gcc -Wall test.c
将警告信息当做错误处理
gcc -Wall -Werror test.c
-D 编译时定义宏、

整个流程:

预处理

汇编

将汇编文件 hello.s 转换为目标文件 hello.o(二进制文件)

生成可执行文件并且输入文件里面的内容

在实际中编译代码
gcc hello.c -o hello
./hello
这两句就行了

静态库和动态库

静态链接和动态链接

链接分为两种:静态链接、动态链接。

1)静态链接
静态链接:由链接器在链接时将库的内容加入到可执行程序中。
优点:
对运行环境的依赖性较小,具有较好的兼容性
缺点:
生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间
库函数有了更新,必须重新编译应用程序

2)动态链接
动态链接:连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序。
优点:
在需要的时候才会调入对应的资源函数
简化程序的升级;有着较小的程序体积
实现进程之间的资源共享(避免重复拷贝)
缺点:
依赖动态库,不能独立运行
动态库依赖版本问题严重

静态库的制作和使用

静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。
按照习惯,一般以“.a”做为文件后缀名。静态库的命名一般分为三个部分:
前缀:lib
库名称:自己定义即可
后缀:.a
所以最终的静态库的名字应该为:libxxx.a


静态库的制作步骤:
1)先把.c的文件生成.o
2)利用打包工具ar将准备好的.o文件打包为.a文件libtest.a
在使用ar工具是时候需要添加参数:rcs
r更新
c创建
s建立索引

静态库的测试

静态库制作完成之后,需要将.a文件和头文件一起发布给用户。
假设测试文件为main.c,静态库文件为libtest.a头文件为head.h

gcc: 调用 GCC 编译器。
test.c: 源代码文件。
-L./: 指定库文件的搜索路径为当前目录(.)。这个选项告诉 GCC 在当前目录下查找库文件。
-I./: 指定头文件的搜索路径为当前目录(.)。这个选项告诉 GCC 在当前目录下查找头文件。
-ltest: 链接 libtest.a 或 libtest.so 库文件。-l 选项用于链接库,test 是库的名称(不需要加 lib 前缀和 .a 或 .so 后缀)。

-I./ 和 -L./ 选项在编译时告诉编译器从当前目录查找头文件和库文件。这对在本地开发时很有用,尤其是在编译依赖于自定义库的程序时。

动态库的制作和使用

动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

按照习惯,一般以“.so”做为文件后缀名。共享库的命名一般分为三个部分:
前缀:lib
库名称:自己定义即可
后缀:.so
步骤一:生成目标文件,此时要加编译选项:-fPIC(fpic)
参数:-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。
步骤二:生成共享库,此时要加链接器选项: -shared(指定生成动态链接库)
步骤三: 通过nm命令查看对应的函数

动态库的测试


这里报错的原因是编译器找不到源文件

  1. 临时设置LD_LIBRARY_PATH:
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
  2. 永久设置,把export LD_LIBRARY_PATH= L D L I B R A R Y P A T H : 库路径,设置到  / . b a s h r c 或者 / e t c / p r o f i l e 文件中 v i m   / . b a s h r c 最后一行添加如下内容 : e x p o r t L D L I B R A R Y P A T H = LD_LIBRARY_PATH:库路径,设置到~/.bashrc或者 /etc/profile文件中 vim ~/.bashrc 最后一行添加如下内容: export LD_LIBRARY_PATH= LDLIBRARYPATH:库路径,设置到 /.bashrc或者/etc/profile文件中vim /.bashrc最后一行添加如下内容:exportLDLIBRARYPATH=LD_LIBRARY_PATH:/home/deng/share/3rd/2share_test
    使环境变量生效
    source ~/.bashrc
    ./test
    3)将其添加到 /etc/ld.so.conf文件中
    编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
    运行sudo ldconfig -v,该命令会重建/etc/ld.so.cache文件
    sudo vim /etc/ld.so.conf
    文件最后添加动态库路径(绝对路径)
    sudo ldconfig -v
  3. 使用符号链接, 但是一定要使用绝对路径
    sudo ln -s /home/deng/test/6share_test/libtest.so /lib/libtest.so

GDB调试器

GDB简介

GNU工具集中的调试器是GDB(GNU Debugger),该程序是一个交互式工具,工作在字符模式。
除gdb外,linux下比较有名的调试器还有xxgdb, ddd, kgdb, ups。
GDB主要完成下面四个方面的功能:

  1. 启动程序,可以按照你的自定义的要求随心所欲的运行程序。
  2. 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
  3. 当程序被停住时,可以检查此时你的程序中所发生的事。
  4. 动态的改变你程序的执行环境。

生成调试信息

一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:
gcc -g hello.c -o hello
g++ -g hello.cpp -o hello
如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。

启动GDB

启动gdb:gdb program
program 也就是执行文件,一般在当前目录下。
设置运行参数
set args 可指定运行时参数。(如:set args 10 20 30 40 50 )
show args 命令可以查看设置好的运行参数。

启动程序:
run: 程序开始执行,如果有断点,停在第一个断点处
start: 程序向下执行一行。

显示源代码

用list命令来打印程序的源代码。默认打印10行。
list linenum: 打印第linenm行的上下文内容.
list function: 显示函数名为function的函数的源程序。
list: 显示当前行后面的源程序。
list -: 显示当前行前面的源程序。
set listsize count:设置一次显示源代码的行数。
show listsize: 查看当前listsize的设置。

断点操作

1)简单断点
break 设置断点,可以简写为b
b 10 设置断点,在源程序第10行
b func 设置断点,在func函数入口处
2)多文件设置断点
C++中可以使用class::function或function(type,type)格式来指定函数名。
如果有名称空间,可以使用namespace::class::function或者function(type,type)格式来指定函数名。
break filename:linenum – 在源文件filename的linenum行处停住
break filename:function – 在源文件filename的function函数的入口处停住
brea
k class::function或function(type,type) – 在类class的function函数的入口处停住
break namespace::class::function – 在名称空间为namespace的类class的function函数的入口处停住
3)查询所有断点
info b
info break
i break
i b

条件断点

一般来说,为断点设置一个条件,我们使用if关键词,后面跟其断点条件。
设置一个条件断点:
b test.c:8 if Value == 5

维护断点

1)delete [range…] 删除指定的断点,其简写命令为d。
如果不指定断点号,则表示删除所有的断点。range表示断点号的范围(如:3-7)。
比删除更好的一种方法是disable停止点,disable了的停止点,GDB不会删除,当你还需要时,enable即可,就好像回收站一样。
2) disable [range…] 使指定断点无效,简写命令是dis。
如果什么都不指定,表示disable所有的停止点。
3) enable [range…] 使无效断点生效,简写命令是ena。
如果什么都不指定,表示enable所有的停止点。

调试代码

run 运行程序,可简写为r
next 单步跟踪,函数调用当作一条简单语句执行,可简写为n
step 单步跟踪,函数调进入被调用函数体内,可简写为s
finish 退出进入的函数
until 在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体,可简写为u。
continue 继续运行程序,停在下一个断点的位置,可简写为c
quit 退出gdb,可简写为q

数据查看

1)查看运行时数据
print 打印变量、字符串、表达式等的值,可简写为p
p count 打印count的值

自动显示

你可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的GDB命令是display。
display 变量名
info display – 查看display设置的自动显示的信息。
undisplay num(info display时显示的编号)
delete display dnums… – 删除自动显示,dnums意为所设置好了的自动显式的编号。如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)
disable display dnums…
enable display dnums…
disable和enalbe不删除自动显示的设置,而只是让其失效和恢复。

查看修改变量的值

1)ptype width – 查看变量width的类型
type = double
2)p width – 打印变量width 的值
$4 = 13
你可以使用set var命令来告诉GDB,width不是你GDB的参数,而是程序的变量名,如:
set var width=47 // 将变量var值设置为47
在你改变程序变量取值时,最好都使用set var格式的GDB命令

Makefile

Makefile是项目管理工具
make主要解决两个问题:

  1. 大量代码的关系维护
    大项目中源代码比较多,手工维护、编译时间长而且编译命令复杂,难以记忆及维护
    把代码维护命令及编译命令写在makefile文件中,然后再用make工具解析此文件自动执行相应命令,可实现代码的合理编译
  2. 减少重复编译时间
    n 在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间

Makefile语法规则

Makefile基本规则三要素:
1)目标:
通常是要产生的文件名称,目标可以是可执行文件或其它obj文件,也可是一个动作的名称
2)依赖文件:
用来输入从而产生目标的文件
一个目标通常有几个依赖文件(可以没有)
3)命令:
make执行的动作,一个规则可以含几个命令(可以没有)
有多个命令时,每个命令占一行

1.mk

输出结果:

Make命令格式

make命令格式:
make [ -f file ][ options ][ targets ]
1.[ -f file ]:
make默认在工作目录中寻找名为GNUmakefile、makefile、Makefile的文件作为makefile输入文件
-f 可以指定以上名字以外的文件作为makefile输入文件
2.[ options ]
-v: 显示make工具的版本信息
-w: 在处理makefile之前和之后显示工作路径
-C dir:读取makefile之前改变工作路径至dir目录
-n:只打印要执行的命令但不执行
-s:执行但不显示执行的命令
3.[ targets ]:
若使用make命令时没有指定目标,则make工具默认会实现makefile文件内的第一个目标,然后退出
指定了make工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)。

make -f 1.mk: 在终端中执行此命令,make 会读取 1.mk 文件并按照其中的规则进行编译和构建

make -C 7makefile/ -f 1.mk 的作用是:
-C 7makefile/:进入 7makefile/ 目录,在该目录中执行 Makefile示例

Makefile简单示例

测试程序: test.c add.c sub.c mul.c div.c

最简单的Makefile
test:test.c add.c sub.c mul.c div.c
gcc test.c add.c sub.c mul.c div.c -o test
缺点:效率低,修改一个文件,所有文件会被全部编译

第二个版本Makefile
test:test.o add.o sub.o mul.o div.o
gcc test.o add.o sub.o mul.o div.o -o test

test.o:test.c
gcc -c test.c
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
mul.o:mul.c
gcc -c mul.c
div.o:div.c
gcc -c div.c命令。
-f 1.mk:指定使用 1.mk 作为 Makefile。

Makefile中的变量

类似于c语言中的宏定义
在Makefile中使用变量有点类似于C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使Makefile易于维护,修改内容变得简单变量定义及使用。

自定义变量
1)定义变量方法:
变量名=变量值
2)引用变量:
( 变量名 ) 或 (变量名)或 (变量名){变量名}
3)makefile的变量名:
makefile变量名可以以数字开头
变量是大小写敏感的
变量一般都在makefile的头部定义
变量几乎可在makefile的任何地方使用
示例:
OBJS=add.o sub.o mul.o div.o test.o
TARGET=test
( T A R G E T ) : (TARGET): (TARGET):(OBJS)
gcc $(OBJS) -o $(TARGET)
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
mul.o:mul.c
gcc -c mul.c -o mul.o
div.o:div.c
gcc -c div.c -o div.o
test.o:test.c
gcc -c test.c -o test.o
clean:
rm -rf $(OBJS) $(TARGET)

Makefile中的三种自动变量

参考示例:

#变量
OBJS=add.o sub.o mul.o div.o test.o add.o
TARGET=test
CC=gcc
#KaTeX parse error: Expected 'EOF', got '#' at position 9: @: 表示目标 #̲<: 表示第一个依赖
#$^: 表示所有的依赖
( T A R G E T ) : (TARGET): (TARGET):(OBJS)
#$(CC) $(OBJS) -o $(TARGET)
$(CC) $^ -o $@
echo $@
echo $<
echo $^
add.o:add.c
$(CC) -c $< -o $@
sub.o:sub.c
$(CC) -c $< -o $@
mul.o:mul.c
$(CC) -c $< -o $@
div.o:div.c
$(CC) -c $< -o $@
test.o:test.c
$(CC) -c $< -o $@
clean:
rm -rf $(OBJS) $(TARGET)

模式规则(模式匹配)

Makefile第三个版本:
OBJS=test.o add.o sub.o mul.o div.o
TARGET=test
( T A R G E T ) : (TARGET): (TARGET):(OBJS)
gcc $(OBJS) -o $(TARGET)
%.o:%.c
gcc -c $< -o $@

Makefile中的两个函数

Makefile第四个版本:
#获取当前目录下所有.c的文件
SRC=KaTeX parse error: Expected 'EOF', got '#' at position 16: (wildcard *.c) #̲将SRC中所有出现.c的替换成…(patsubst %.c, %.o, $(SRC))
TARGET=test
( T A R G E T ) : (TARGET): (TARGET):(OBJS)
gcc $(OBJS) -o $(TARGET)

%.o:%.c
gcc -c $< -o $@

Makefile中的伪目标

clean用途: 清除编译生成的中间.o文件和最终目标文件
make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令,解决方案:
伪目标声明: .PHONY:clean
声明目标为伪目标之后,makefile将不会该判断目标是否存在或者该目标是否需要更新
clean命令中的特殊符号:
“-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
“@”不显示命令本身,只显示结果。如:“@echo clean done”

Makefile第五个版本:
SRC= ( w i l d c a r d ∗ . c ) O B J S = (wildcard *.c) OBJS= (wildcard.c)OBJS=(patsubst %.c, %.o, $(SRC))
TARGET=test
( T A R G E T ) : (TARGET): (TARGET):(OBJS)
gcc $(OBJS) -o $(TARGET)

%.o:%.c
gcc -c $< -o $@
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGET)

Makefile的工作原理

1)若想生成目标, 检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来 生成该依赖文件
2)检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新

系统调用简介和实现

系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务,比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。
从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口——它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。
系统服务之所以需要通过系统调用来提供给用户空间的根本原因是为了对系统进行“保护”,因为我们知道 Linux 的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离。
所以用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数。
比如我们熟悉的“hello world”程序(执行时)就是标准的用户空间进程,它使用的打印函数 printf 就属于用户空间函数,打印的字符“hello word”字符串也属于用户空间数据。
但是很多情况下,用户进程需要获得系统服务(调用系统程序),这时就必须利用系统提供给用户的“特殊接口”——系统调用了,它的特殊性主要在于规定了用户进程进入内核的具体位置。
换句话说,用户访问内核的路径是事先规定好的,只能从规定位置进入内核,而不准许肆意跳入内核。有了这样的陷入内核的统一访问路径限制才能保证内核安全无误。我们可以形象地描述这种机制:作为一个游客,你可以买票要求进入野生动物园,但你必须老老实实地坐在观光车上,按照规定的路线观光游览。当然,不准下车,因为那样太危险,不是让你丢掉小命,就是让你吓坏了野生动物。
系统调用的实现
系统调用是属于操作系统内核的一部分的,必须以某种方式提供给进程让它们去调用。CPU 可以在不同的特权级别下运行,而相应的操作系统也有不同的运行级别,用户态和内核态。运行在内核态的进程可以毫无限制的访问各种资源,而在用户态下的用户进程的各种操作都有着限制,比如不能随意的访问内存、不能开闭中断以及切换运行的特权级别。显然,属于内核的系统调用一定是运行在内核态下,但是如何切换到内核态呢?
答案是软件中断。软件中断和我们常说的中断(硬件中断)不同之处在于,它是通过软件指令触发而并非外设引发的中断,也就是说,又是编程人员开发出的一种异常(该异常为正常的异常)。操作系统一般是通过软件中断从用户态切换到内核态

系统调用和库函数的区别

Linux 下对文件操作有两种方式:系统调用(system call)和库函数调用(Library functions)。
库函数由两类函数组成:
1)不需要调用系统调用
不需要切换到内核空间即可完成函数全部功能,并且将结果反馈给应用程序,如strcpy、bzero 等字符串操作函数。
2)需要调用系统调用
需要切换到内核空间,这类函数通过封装系统调用去实现相应功能,如 printf、fread等。
系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。当运行内核代码时,CPU工作在内核态,在系统调用发生前需要保存用户态的栈和内存环境,然后转入内核态工作。系统调用结束后,又要切换回用户态。这种环境的切换会消耗掉许多时间 。

C库中IO函数工作流程


为什么需要缓冲区?
答:速度匹配
CPU读数据是非常快的,从磁盘拿到内存非常慢。缓冲区是为了提高效率的。

库函数访问文件的时候根据需要,设置不同类型的缓冲区,从而减少了直接调用 IO 系统调用的次数,提高了访问效率。
这个过程类似于快递员给某个区域(内核空间)送快递一样,快递员有两种方式送:
1)来一个快递就马上送到目的地,来一件送一件,这样导致来回走比较频繁(系统调用)
2)等快递攒着差不多后(缓冲区),才一次性送到目的地(库函数调用)
这样看来第二种方法好一点

错误处理函数

errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。查看错误代码errno是调试程序的一个重要方法。
当Linux C api函数发生异常时,一般会将errno全局变量赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。

测试程序

#include <stdio.h>  //fopen
#include <errno.h>  //errno
#include <string.h> //strerror(errno)

//errno是一个全局变量 
//errno是保存系统最近出错错误码

int main()
{
    FILE *fp = fopen("txt", "r");
    if (NULL == fp)
    {
        printf("%d\n", errno);  //打印错误码
        //打印两次错误信息 这两种都可以
        printf("%s\n", strerror(errno)); //把errno的数字转换成相应的文字
        perror("fopen err");    //打印错误原因的字符串

        return 1;
    }

    return 0;
}      

虚拟地址空间

每个进程都会分配虚拟地址空间,在32位机器上,该地址空间为4G 。

文件描述符

文件描述符
在 Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数(I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。

打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。

程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。

#define STDIN_FILENO 0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符
在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。

最大打开的文件个数
Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用 close() 函数关闭文件。
查看当前系统允许打开最大文件个数:
cat /proc/sys/fs/file-max
当前默认设置最大打开文件个数1024
ulimit -a
修改默认设置最大打开文件个数为4096
ulimit -n 4096

常用的文件操作函数

open函数

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>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
    mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
返回值:
    成功:成功返回打开的文件描述符
    失败:-1

flags详细说明
必选项:
取值 含义
O_RDONLY 以只读的方式打开
O_WRONLY 以只写的方式打开
O_RDWR 以可读、可写的方式打开
可选项,和必选项按位或起来
取值 含义
O_CREAT 文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL 如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC 如果文件存在,则清空文件内容
O_APPEND 写文件时,数据添加到文件末尾
O_NONBLOCK 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O

close函数

#include <unistd.h>int close(int fd);
功能:
    关闭已打开的文件
参数:
    fd : 文件描述符,open()的返回值
返回值:
    成功:0
    失败: -1, 并设置errno

打开文件是占用资源,关闭文件是释放资源,系统调用的接口不占用缓冲区

案例
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
        int fd=-1;

        //以只读的方式打开 如果文件不存在报错
        fd=open("txt",O_RDONLY);
        if(-1==fd)
        {
                perror("open");
                return 1;
        }

        printf("fd=%d\n",fd);

        //关闭文件 参数为文件描述符
        close(fd);

        return 0;
}


fd打印结果为3,表面没打开一个新的文件,文件描述符占用的是空闲的最小的一个

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
        int fd=-1;

        //1. 以只读的方式打开 如果文件不存在报错
        //fd=open("txt",O_RDONLY);
        
        //2. 以只写的方式打开一个文件 如果文件存在就直接打开 如果文件不存在就新建一个文件
        fd=open("txt",O_WRONLY|O_CREAT,0644);
        if(-1==fd)
        {
                perror("open");
                return 1;
        }

        printf("fd=%d\n",fd);

        //关闭文件 参数为文件描述符
        close(fd);

        return 0;
}

具体的权限和它们对应的数值是:
读 ® = 4
写 (w) = 2
执行 (x) = 1
0644 这个八进制数字的含义可以分解如下:
第一位(0)表示特殊权限位,通常是 0,表示没有特殊权限。
第二位(6)表示文件拥有者的权限:
6 是 4(读权限)+ 2(写权限),即文件拥有者具有读和写的权限。
第三位(4)表示文件所属组的权限:
4 表示只有读权限。
第四位(4)表示其他用户(即与该文件没有任何直接关系的用户)的权限:
4 表示只有读权限。
所以,0644 表示:
文件拥有者可以读写文件(rw-),
文件所属组可以读文件(r–),
其他用户也可以读文件(r–)。

其他用法:

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
        int fd=-1;

        //1. 以只读的方式打开 如果文件不存在报错
        //fd=open("txt",O_RDONLY);

        //2. 以只写的方式打开一个文件 如果文件存在就直接打开 如果文件不存在就新建一个文件
        //fd=open("txt",O_WRONLY|O_CREAT,0644);

        //3. 以只写的方式打开i一个文件,如果文件存在就报错,如果文件不存在就新建一个文件
        //fd=open("txt",O_WRONLY|O_CREAT|O_EXCL,0644);

        //4. 以读写的方式打开一个文件 如果文件存在就直接打开 如果文件不存在就新建一个文件
        //fd=open("txt",O_RDWR|O_CREAT,0644);

        //5. O_TRUNC 清空文件内容 
        //如果文件不存在就新建一个文件 如果文件存在就打开后清空
        //fd=open("txt",O_WRONLY|O_TRUNC|O_CREAT,0644);

        //6.O_APPEND 追加的方式
        //以只写的方式和追加的方式打开一个文件 如果文件不存在就会报错
        //fd=open("txt",O_WRONLY|O_APPEND);
        if(-1==fd)
        {
                perror("open");
                return 1;
        }

        printf("fd=%d\n",fd);

        //关闭文件 参数为文件描述符
        close(fd);

        return 0;
}

write函数

write函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:
    把指定数目的数据写到文件(fd)
参数:
    fd :  文件描述符
    buf : 数据首地址
    count : 写入数据的长度(字节)
返回值:
    成功:实际写入数据的字节个数
    失败: - 1
案例
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main(void)
{
    int fd = -1;
    int ret = -1;
    char *str="hello itcast";

    //1. 打开文件
    fd = open("txt", O_WRONLY | O_CREAT , 0644);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }

    printf("fd = %d\n", fd);

    //2. 写文件
    //write 函数返回写入的字节数
    ret = write(fd,str,strlen(str));
    if (-1==ret)
    {
       perror("write");
       return 1;
    }
    printf("write len:%d\n",ret);


    //3. 关闭文件
    close(fd);
    return 0;
}

read函数

#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
功能:
    把指定数目的数据读到内存(缓冲区)
参数:
    fd : 文件描述符
    buf : 内存首地址
    count : 读取的字节个数
返回值:
    成功:实际读取到的字节个数
    失败: - 1
案例
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define SIZE 128

//从文件中读数据
int main(void)
{
    int fd = -1;
    int ret = -1;

    char buf[SIZE];

    //1. 打开文件  只读的方式
    fd = open("txt", O_RDONLY);
    if (-1 == fd)
    {
        perror("open"); 
        return 1;
    }
    printf("open successfully fd = %d\n", fd);

    //2. 读文件
    memset(buf, 0, SIZE);
    //从文件中最多读取SIZE个字节保存到buf中
    ret = read(fd, buf, SIZE);
    if (-1 == ret)
    {
        perror("read"); 
        close(fd);
        return 1;
    }

    printf("buf: %s\n", buf);

    //3. 关闭文件
    close(fd);
    return 0;
}

memset(buf, 0, SIZE) 将缓冲区 buf 的每个字节设置为 0,以确保在读取数据前缓冲区是干净的。
read(fd, buf, SIZE) 从文件中最多读取 SIZE 个字节的数据到 buf 中,返回值 ret 表示实际读取到的字节数。如果 ret == -1,表示读取失败;否则表示成功读取的字节数。

阻塞和非阻塞

阻塞:进程去读文件 数据为空会被阻塞(读写都会被阻塞)
非阻塞:进程去读文件 数据非空

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。
从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。
同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。
【注意】阻塞与非阻塞是对于文件而言的,而不是指read、write等的属性。

简单案例

#include <stdio.h>
 
int main()
{
        char ch=-1;

        //从标准输入获取一个字符
        ch=getchar();//这句阻塞了

        putchar(ch);

        return 0;
}

lseek函数

#include <sys/types.h>
#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);
功能:
    改变文件的偏移量
参数:
    fd:文件描述符
    offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
​
    whence:其取值如下:
        SEEK_SET:从文件开头移动offset个字节
        SEEK_CUR:从当前位置移动offset个字节
        SEEK_END:从文件末尾移动offset个字节
返回值:
    若lseek成功执行, 则返回新的偏移量
    如果失败, 返回-1
 

所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。

读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。

案例分析

int main(void)
{
    int fd = -1;
    int ret = -1;

    char buf[SIZE];

    //1. 打开文件
    fd = open("txt", O_RDWR | O_CREAT, 0644);
    if (-1 == fd)
    {
        perror("open");
        return 1;
    }
    printf("fd=%d\n",fd);

    //2. lseek操作
    write(fd,"ABCDEFG",7);


    //将文件开头偏移32个字节
    ret = lseek(fd,32,SEEK_SET);
    if (-1 == ret)
    {
        perror("lseek");
        return 1;
    }

    write(fd, "1234567890", 10);

    memset(buf,0,SIZE);
    ret=read(fd,buf,SIZE);
    printf("read ret:%  buf: %s\n",ret,buf);


    //关闭文件
    close(fd);

    return 0;
}


为什么没有读出?
在文件写入 “ABCDEFG” 和 “1234567890” 之后,文件指针位于文件末尾,没有将文件指针重置为文件的开头。因此,读操作从文件末尾开始,没有可读取的内容。
在memset那条语句之前加入lseek(fd,0,SEEK_SET)表示将文件位置指针指向文件开头

文件目录操作函数

stat

stat函数(重点)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>int stat(const char *path, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
功能:
    获取文件状态信息
    stat和lstat的区别:
        当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;(符号链接相当于快捷方式)
        而stat返回的是该链接指向的文件的信息。
参数:
    path:文件名
    buf:保存文件信息的结构体
返回值:
    成功: 0
    失败: -1

文件信息的查看例如:

struct stat结构体说明:

struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};

简单案例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#if 0
struct stat {
    dev_t     st_dev;         /* ID of device containing file */
    ino_t     st_ino;         /* inode number */
    mode_t    st_mode;        /* protection */
    nlink_t   st_nlink;       /* number of hard links */
    uid_t     st_uid;         /* user ID of owner */
    gid_t     st_gid;         /* group ID of owner */
    dev_t     st_rdev;        /* device ID (if special file) */
    off_t     st_size;        /* total size, in bytes */
    blksize_t st_blksize;     /* blocksize for filesystem I/O */
    blkcnt_t  st_blocks;      /* number of 512B blocks allocated */

    /* Since Linux 2.6, the kernel supports nanosecond
     *                   precision for the following timestamp fields.
     *                                     For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* time of last access */
    struct timespec st_mtim;  /* time of last modification */
    struct timespec st_ctim;  /* time of last status change */

#define st_atime st_atim.tv_sec      /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};

#endif

//查看文件信息命令
//deng@itcast:~/share/4th$ stat txt

//获取文件相关信息
int main(void)
{
    int ret = -1;

    struct stat buf;

    memset(&buf, 0, sizeof(buf));
    //获取文件相关信息
    ret = stat("txt", &buf);
    if (-1 == ret)
    {
        perror("stat"); 
        return 1;
    }

    printf("st_dev: %lu\n", buf.st_dev);
    printf("st_ino: %lu\n", buf.st_ino);
    printf("st_nlink: %lu\n", buf.st_nlink);
    printf("st_uid: %d\n", buf.st_uid);
    printf("st_gid: %d\n", buf.st_gid);
    printf("st_rdev:%lu\n", buf.st_rdev);
    printf("st_size: %ld\n", buf.st_size);
    printf("st_blksize: %ld\n", buf.st_blksize);
    printf("st_blocks: %ld\n", buf.st_blocks);


    return 0;
}

stat显示文件类型

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int show_file_type(struct stat* s)
{
    switch (s->st_mode & S_IFMT)
    {
        //普通文件
    case S_IFREG:
        printf("该文件是普通文件\n");
        break;
        //目录
    case S_IFDIR:
        printf("该文件是目录\n");
        break;
        //字符设备
    case S_IFCHR:
        printf("该文件是字符设备\n");
        break;
        //块设备
    case S_IFBLK:
        printf("该文件是块设备\n");
        break;
        //套接字
    case S_IFSOCK:
        printf("该文件是套接字\n");
        break;
        //管道
    case S_IFIFO:
        printf("该文件是管道\n");
        break;
        //符号链接
    case S_IFLNK:
        printf("该文件是符号链接\n");
        break;
    default:
        printf("未知的文件类型\n");

    }
}

int main(int argc, char **argv)
{
    int ret = -1;

    struct stat s;

    if (2 != argc)
    {
        printf("usage: ./a.out filename\n"); 
        return 1;
    }
    
    //获取文件相关信息
    ret = stat(argv[1], &s);
    if (-1 == ret)
    {
        perror("stat"); 
        return 1;
    }

    //显示文件类型
    show_file_type(&s);
    return 0;
}

s->st_mode
s->st_mode 是 stat 结构体中的一个成员,它是一个位掩码,包含了文件的各种信息,包括权限和文件类型。
这个成员中的不同位表示不同的信息(如文件类型、读写权限等)。
S_IFMT
S_IFMT 是一个常量,用来屏蔽其他位,只保留表示文件类型的那几位。

& 运算
& 是按位与运算符,它对两个操作数的每一位进行与操作:
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
在这里使用 & 是为了从 st_mode 中提取文件类型的相关位。通过 s->st_mode & S_IFMT,你只会保留与 S_IFMT 中对应的那几位(即表示文件类型的位),其他位都会被置为 0。

stat 函数原型:
int stat(const char *pathname, struct stat *buf);
stat 函数用于获取指定文件的相关状态信息(例如文件类型、大小、权限等),并将这些信息存储在一个 struct stat 类型的结构体中。

stat显示文件第二个版本

直接使用文件的宏
例子:

if(S_ISREG(s->st_mode))
{
	printf("是普通文件\n");
}

stat获取文件的权限


s->st_mode: 访问文件的模式信息,这通常是从 stat 结构中获取的。
S_IRUSR: 常量,用于检查文件拥有者的读取权限。
&: 位与运算,用于检查用户读权限的位是否被设置。

USR GRP OTH
文件所属者
文件所属组
其他人

其他操作函数

access函数
#include <unistd.h>

int access(const char *pathname, int mode);
功能:测试指定文件是否具有某种属性
参数:
pathname:文件名
mode:文件权限,4种权限
R_OK: 是否有读权限
W_OK: 是否有写权限
X_OK: 是否有执行权限
F_OK: 测试文件是否存在
返回值:
0: 有某种权限,或者文件存在
-1:没有,或文件不存在
access(“txt”, F_OK);

chmod函数
#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
功能:修改文件权限
参数:
filename:文件名
mode:权限(8进制数)
返回值:
成功:0
失败:-1

chown函数
#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);
功能:修改文件所有者和所属组
参数:
pathname:文件或目录名
owner:文件所有者id,通过查看 /etc/passwd 得到所有者id
group:文件所属组id,通过查看 /etc/group 得到用户组id
返回值:
成功:0
失败:-1

truncate函数
#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
功能:修改文件大小
参数:
path:文件文件名字
length:指定的文件大小
a)比原来小, 删掉后边的部分
b)比原来大, 向后拓展
返回值:
成功:0
失败:-1

link函数
#include <unistd.h>

int link(const char *oldpath, const char *newpath);
功能:创建一个硬链接
参数:
oldpath:源文件名字
newpath:硬链接名字
返回值:
成功:0
失败:-1

symlink函数
#include <unistd.h>

int symlink(const char *target, const char *linkpath);
功能:创建一个软链接
参数:
target:源文件名字
linkpath:软链接名字
返回值:
成功:0
失败:-1

readlink函数
#include <unistd.h>

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:读软连接对应的文件名,不是读内容(该函数只能读软链接文件)
参数:
pathname:软连接名
buf:存放软件对应的文件名
bufsiz :缓冲区大小(第二个参数存放的最大字节数)
返回值:
成功:>0,读到buf中的字符个数
失败:-1

unlink函数
#include <unistd.h>

int unlink(const char *pathname);
功能:删除一个文件(软硬链接文件)
参数:
pathname:删除的文件名字
返回值:
成功:0
失败:-1

rename函数
#include <stdio.h>

int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数:
oldpath:旧文件名
newpath:新文件名
返回值:
成功:0
失败:-1

文件描述符复制

这里的使用场景是在文件重定向中

文件的重定向有输出重定向和输入重定向
案例:

  1. 输出重定向
    输出重定向是将程序的输出结果(通常显示在终端或控制台上)写入到一个文件中,而不是显示在屏幕上。典型的用法是将标准输出(stdout)或标准错误输出(stderr)重定向到文件。

常见的符号:

:重定向输出到文件,覆盖原文件内容。

:重定向输出到文件,追加到文件末尾。
示例:
假设你有一个程序 example_program,它会输出一些信息到控制台。如果你想将这些输出保存到一个文件中而不是显示在控制台上,你可以使用输出重定向。

./example_program > output.txt
上面这条命令会将 example_program 的输出重定向到 output.txt,如果 output.txt 存在,它的内容会被覆盖。
如果你想将输出追加到文件末尾,而不是覆盖它,你可以使用 >>:
./example_program >> output.txt
这会将输出追加到文件末尾,而不会覆盖已有内容。

  1. 输入重定向
    输入重定向是将一个文件作为程序的输入,而不是从标准输入(stdin,通常是键盘)读取数据。

常见的符号:
<:从文件中读取输入。
示例:
如果有一个程序 example_program 需要用户输入数据,而你想从一个文件中读取这些输入数据,你可以这样做:
./example_program < input.txt
这会将 input.txt 的内容作为 example_program 的输入,而不是从键盘读取。

  1. 错误重定向
    你还可以重定向标准错误输出(stderr),这通常用于将错误信息记录到文件中。
    常见的符号:
    2>:将标准错误输出重定向到文件。
    2>>:将标准错误输出追加到文件末尾。
    示例:
    ./example_program 2> error_log.txt
    这会将 example_program 的错误信息输出到 error_log.txt,如果有错误信息,它们会被写入这个文件中。

如果你想同时将标准输出和标准错误输出都重定向到同一个文件,你可以使用:
./example_program > output.txt 2>&1

dup函数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

//文件描述符复制
int main(void)
{
    int fd = -1;
    int newfd = -1;

    //1. 打开文件
    fd = open("txt", O_RDWR | O_CREAT, 0644);
    if (-1 == fd)
    {
        perror("open"); 
        return 1;
    }

    printf("fd=%d\n", fd);

    //2. 复制文件描述符
    newfd = dup(fd);
    if (-1 == newfd)
    {
        perror("dup"); 
        return 1;
    }
    printf("newfd=%d\n", newfd);

    write(fd, "ABCDEFG", 7);
    write(newfd, "1234567", 7);

    //3. 关闭文件
    close(fd);
    close(newfd);

    return 0;
}

通过 dup 复制文件描述符,fd 和 newfd 都指向相同的文件并共享相同的文件偏移量,因此两次写操作会依次写入文件。
Unix 和类 Unix 系统中,文件描述符是用于标识打开的文件、管道、套接字等的整数值。文件描述符通常从 0 开始,依次递增。默认情况下,标准输入(stdin)、标准输出(stdout)和标准错误(stderr)的文件描述符分别是 0、1 和 2。
当你使用 open 函数打开一个文件时,它会返回一个新的文件描述符,通常是最小的尚未使用的文件描述符。文件描述符的分配遵循以下规则:
标准文件描述符:文件描述符 0、1 和 2 分别对应于标准输入、标准输出和标准错误。
新文件描述符:open 函数返回的文件描述符是最小的尚未使用的文件描述符。在打开文件之前,如果没有其他文件描述符被打开,open 将返回 3,因为 0、1 和 2 已经被标准输入、标准输出和标准错误占用了。
如果 fd 的值是 3,这意味着文件描述符 0、1 和 2 已经被标准输入、标准输出和标准错误占用,因此 open 函数分配了下一个最小的可用文件描述符,也就是 3。

dup2函数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

//文件描述符复制
int main(void)
{
    int fd = -1;
    int newfd = -1;

    //1. 打开文件 Ctrl + P
    fd = open("txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (-1 == fd)
    {
        perror("open"); 
        return 1;
    }

    //2. 复制文件描述符
    newfd = 2;
    //如果newfd文件描述符已经与其它文件关联, 那么先close, 然后在使用该数字
    newfd = dup2(fd, newfd);
    if (-1 == newfd)
    {
        perror("dup"); 
        return 1;
    }

    printf("fd : %d  newfd: %d\n", fd, newfd);

    write(fd, "123456789", 9);
    write(newfd, "ABCDEFGHI", 9);

    
    //3. 关闭文件
    close(fd);
    close(newfd);

    return 0;
}

fcntl函数

 fcnlt函数
#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */);
功能:改变已打开的文件性质,fcntl针对描述符提供控制。
参数:
    fd:操作的文件描述符
    cmd:操作方式
    arg:针对cmd的值,fcntl能够接受第三个参数int arg。
返回值:
    成功:返回某个其他值
    失败:-1

fcntl函数有5种功能:

  1. 复制一个现有的描述符(cmd=F_DUPFD)
  2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  5. 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)

实现文件描述符复制

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

//文件描述符复制
int main(void)
{
    int fd = -1;
    int newfd = -1;

    //1. 打开文件 
    fd = open("txt", O_RDWR | O_CREAT, 0644);
    if (-1 == fd)
    {
        perror("open"); 
        return 1;
    }

    //2. 复制文件描述符 
    //功能等价于dup函数
    newfd = fcntl(fd, F_DUPFD, 0);
    if (-1 == newfd)
    {
        perror("fcntl"); 
        return 1;
    }
    printf("fd : %d  newfd: %d\n", fd, newfd);

    //3. 写文件  共享偏移量
    write(fd, "123456789", 9);
    write(newfd, "ABCDEFGHI", 9);

    
    //4. 关闭文件
    close(fd);
    close(newfd);

    return 0;
}

这样打印的结果不会覆盖 因为打开了一次

但是下面这个场景的情况 txt 会被覆盖掉

实现文件状态标记获取和位置

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

//文件描述符复制
int main(void)
{
    int fd = -1; 
    int ret = -1;

    //1. 打开文件 
    fd = open("txt", O_RDWR | O_CREAT, 0644);
    if (-1 == fd)
    {
        perror("open"); 
        return 1;
    }


    //2. 获取文件状态标志
    ret = fcntl(fd, F_GETFL);
    if (-1 == ret)
    {
        perror("fcntl"); 
        return 1;
    }

    if (ret & O_APPEND)
    {
        printf("before append\n"); 
    }
    else
    {
        printf("before not append\n"); 
    }

    //3. 设置文件状态标记
    ret = ret | O_APPEND;
    ret = fcntl(fd, F_SETFL, ret);
    if (-1 == ret)
    {
        perror("fcntl"); 
        return 1;
    }

    //4. 获取文件状态标志
    ret = fcntl(fd, F_GETFL);
    if (-1 == ret)
    {
        perror("fcntl"); 
        return 1;
    }

    if (ret & O_APPEND)
    {
        printf("after append\n");
    }
    else
    {
        printf("after not append\n");
    }  
        
    //5. 关闭文件
    close(fd);

    return 0;
}

第二步的作用(检查文件当前状态)
第三步的作用(修改文件状态)
第四步的作用(验证修改是否成功

目录相关操作

getcwd函数
#include <unistd.h>char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作目录
参数:
    buf : 缓冲区,存储当前的工作目录
    size : 缓冲区大小
返回值:
    成功:buf中保存当前进程工作目录位置
    失败:NULL
 

chdir函数
#include <unistd.h>int chdir(const char *path);
功能:修改当前进程(应用程序)的路径
参数:
    path:切换的路径
返回值:
    成功:0
    失败:-1


子进程改变工作路径不改变父进程 这里pwd后的路径是不变的

打开和关闭目录

opendir函数
#include <sys/types.h>
#include <dirent.h>
​
DIR *opendir(const char *name);
功能:打开一个目录
参数:
    name:目录名
返回值:
    成功:返回指向该目录结构体指针
    失败:NULL
​
closedir函数
#include <sys/types.h>
#include <dirent.h>int closedir(DIR *dirp);
功能:关闭目录
参数:
    dirp:opendir返回的指针
返回值:
    成功:0
    失败:-1

读目录

readdir函数
#include <dirent.h>struct dirent *readdir(DIR *dirp);
功能:读取目录
参数:
    dirp:opendir的返回值
返回值:
    成功:目录结构体指针
    失败:NULL
    
​相关结构体说明:
struct dirent
{
    ino_t d_ino;                  // 此目录进入点的inode
    off_t d_off;                    // 目录文件开头至此目录进入点的位移
    signed short int d_reclen;      // d_name 的长度, 不包含NULL 字符
    unsigned char d_type;           // d_type 所指的文件类型 
    char d_name[256];               // 文件名
};

d_type文件类型说明:
取值 含义
DT_BLK 块设备
DT_CHR 字符设备
DT_DIR 目录
DT_LNK 软链接
DT_FIFO 管道
DT_REG 普通文件
DT_SOCK 套接字
DT_UNKNOWN 未知

本文标签: 自用基础Linux