学习课程:
基础部分
Shell
命令解释器,格局输入的命令执行相应的命令。查询当前操作系统有哪些shell:
查询当前系统正在使用的shell:
终端
Computer terminal,是一系列输入输出的总称。常见的输入终端有,键盘,话筒,摄像头,常见的输出终端有屏幕、打印机、扬声器。在Ubuntu上的终端程序,就是一个模拟的终端系统。
Shell家族
复制 /bin/sh 被/bin/bash取代
/bin/bash linux默认shell
/bin/ksh Kornshell 有AT&T Bell lab.发展出来的,兼容bash
/bin/tcsh 战争和c shell,提供更的功能
/bin/csh 被/bin/tcsh取代
/bin/zsh 基于ksh发展的更为强大的shell
Bash
Unix shell的一种,在1987年由布莱恩·福克斯为了GNU计划而编写。1989年发布第一个正式版本,原先是计划用在GNU操作系统上,但能运行于大多数类Unix系统的操作系统之上,包括Linux与Mac OS X v10.4起至macOS Mojave都将它作为默认shell,而自macOS Catalina,默认Shell以zsh取代。
Bash是Bourne shell的后继兼容版本与开放源代码版本,它的名称来自Bourne shell(sh)的一个双关语(Bourne again / born again):Bourne-Again SHell。
Bash是一个命令处理器,通常运行于文本窗口中,并能执行用户直接输入的命令。Bash还能从文件中读取命令,这样的文件称为脚本。和其他Unix shell 一样,它支持文件名替换(通配符匹配)、管道、here文档、命令替换、变量,以及条件判断和循环遍历的结构控制语句。包括关键字、语法在内的基本特性全部是从sh借鉴过来的。其他特性,例如历史命令,是从csh和ksh借鉴而来。总的来说,Bash虽然是一个满足POSIX规范的shell,但有很多扩展。
命令和路径补齐
在bash下敲命令时,Tab键可以补全已经巧了一部分的文件名、命令名、目录名。在Ubuntu系统下默认启用bash completion:
复制 source /etc/bash_completion
将此行加入到 ~/.bashrc
启动脚本中,即可启动补全功能。
复制 his + tab 如果以his开头的命令只有一个就会补全这个命令
hi + double tab 列出所有以hi开头的命令
li + t + tab 补全那个以t开头的文件名称
主键盘快捷键
功能快捷键助记上Ctrl-p下Ctrl-n左Ctrl-b右Ctrl-fdelete 光标后面的DelCtrl-dHomeCtrl-afirst letterEndCtrl-eendBackspaceBackspacedelete光标前面的单个字符清除整行Ctrl-u删除光标到行末Ctrl-k显示上滚Shift-PgUp显示下滚Shift-PgDn增大终端字体Ctrl-Shift-+减小终端字体Ctrl- –新打开一个终端Ctrl-Alt-T清屏Ctrl-l
Linux命令大致可以分为三类:
命令格式:
复制 命令名称 --选项 参数
ls --all /etc
命令名称 -选项缩写 参数
ls -a /etc
隐藏终端提示符
vi ~./bash
打开使用的shell环境配置文件,末尾添加 PS1=$
保存退出,重启终端即可。注意 PS1
变量就是提示符的定义,可以根据需要修改。
目录和文件
类Uninx系统目录结构
Linux没有盘符的概念,只有一个根目录,所有文件都在 / 根目录下:
复制 / 根目录
bin // 系统可执行程序
boot // 内核启动程序,所有启动相关文件都放在这儿
grub // 引导器相关文件
dev // 设备文件,比如鼠标的设备文件就是 /dev/mice
etc // 系统软件的启动和配置文件,系统在启动中需要读取的文件都在这个目录。比如账户名密码 vi /etc/passwd
home // 用户主目录,每个用户在此文件夹下都有一个属于自己的家目录
lib // 系统程序库文件,这个目录下存放着系统最基本的动态链接共享库,类似Windows的system32目录,几乎所有的应用程序都会用到这些共享库
media // 挂在媒体设备,比如光驱、U盘
mnt // 临时挂载文件系统,比如挂在Windows的某个分区,Ubuntu默认还是挂在/media目录
opt // 可选的应用软件包,较少使用
proc // 系统内存映射目录,可以访问此目录来获取系统信息。也就是说这个目录的内容是存储在内存中的,而不是硬盘。
root // 管理员宿主目录(家目录)
run // 一个临时文件系统,存储系统启动以来的信息。当系统重启时,这个目录下的文件应该被删掉或清除。如果你的系统上有 /var/run 目录,应该让它指向 run
sbin // 管理员系统程序
selinux // 这个目录是 Redhat/CentOS 所特有的目录,Selinux 是一个安全机制,类似于 windows 的防火墙,但是这套机制比较复杂,这个目录就是存放selinux相关的文件的
srv // 该目录存放一些服务启动之后需要提取的数据
sys // udev用到的设备目录树,/sys反应你的机器当前所接的设备
tmp // 存放一些临时文件
usr // unix shared resources(共享资源) 的缩写,这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似于 windows 下的 program files 目录
bin // 系统用户使用的应用程序
sbin // 超级用户使用的比较高级的管理程序和系统守护程序
src // 内核源代码默认的放置目录
lib // 应用程序库文件
var // 是 variable(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件
相对路径与绝对路径
从根 / 目录开始的路径名为绝对路径,如:
从当前位置开始描述的路径为相对路径:
. 和 ..
复制 . 当前目录
.. 当前目录的上一级目录
/ 根目录
linux八种文件类型
cd 切换目录
命令英文原意:change directory
复制 cd / 切换跟目录
cd .. 切换到上一级目录
cd ../test 切换到当前目录的平级目录test
cd ./test 切换到当前目录的子目录test
cd test 切换到当前目录的子目录test
ls 浏览目录文件
命令英文原意:list
复制 ls 选项[-ald] [文件或目录]
-a 显示所有文件,包括隐藏文件
-l 详细信息显示
-d 只查看目录
-R 递归展示详细信息,如果有目录就展示目录下的文件信息
-h hunman,文件大小展示比较容易看,比如M,G
ls -a 查看所有文件
ls -al 查看所有文件的详细信息
ls -al test 查看当前路径的test目录下的文件
ls -al
drwxrwxr-x 7 root root 4096 1月 12 22:25 redis-6.0.10
文件权限 硬链接计数 所有者 所属组 大小 时间 文件名/文件夹名
1234567890
1代表文件类型
234代表所有者读写执行权限
567代表同组用户读写执行权限
890代表其他人读写执行权限
pwd 展示当前所在的路径
命令英文原意:print working directory
mkdir 创建目录
命令英文原意:make directory
复制 mkdir [目录名]
mkdir newdir
rmdir 删除空目录
命令英文原意:remove directory
复制 rmdir [目录名]
rmdir newdir
touch 创建空文件
rm 删除文件
英文原意,remove
复制 rm filename 删除文件
rm -r dirname 递归删除目录
rm -rf dirname 强制删除,不提示
rm –f *.txt 强制删除当前路径下的所有的txt文件
-f :就是force的意思,忽略不存在的文件,不会出现警告消息
-i :互动模式,在删除前会询问用户是否操作
cp 拷贝文件或者目录
英文原译, copy
复制 cp filename dirname 复制文件到目录
cp filename1 filename2 复制文件1并重命名为文件2
cp -a dirname1 dirname2 复制目录1及其下所有文件到目录2
cp -r dirname1 dirname2 递归复制目录1到目录2
这里-a和-r的差别在于,-a是完全复制,文件权限,改动时间什么的也吧完全相同。
mv 移动文件和目录
复制 mv filename dirname 移动文件到指定目录
mv filename filename 移动文件到指定路劲并改名
cat 查看文件
命令英文原意:concatenate and display files, 连接文件并打印到标准输出设备上
复制 cat 读取终端,直接回显
cat filename 在屏幕上显示文件内容
cat filename1 filename2 在屏幕上显示filename1和filename2文件内容
cat -n filename 在屏幕上显示文件内容,并添加行号
cat -E filename 在屏幕上显示文件内容,并在每行结尾添加一个$
tac 查看文件反向
与cat命令功能类似,但是是反向打印
more 分页显示文件内容
复制 more filename
(空格) 或f 显示下一页
(Enter) 显示下一行
q或Q 退出
less 分页显示文件内容
复制 less [文件名]
(空格) 或f 显示下一页
(Enter) 显示下一行
q或Q 退出
除此之外,还可以使用方向键上下滚动文件
head 显示文件前几行的内容
复制 head -n filename 查看文件前n行
tail 显示文件后几行的内容
复制 tail [参数] <文件名>
-n:显示后n行,不指定此参数显示后10行
+n:从第n行显示到文件尾
-F:用于跟踪显示不断增长的文件结尾内容
tail -n filename 查看文件后n行
tree 查看目录结构树
复制 非linux自带命令,需要安装
sudo apt-get install tree
yum install tree
tree
[root@s1 redis6]# tree
.
└── bin
├── redis-benchmark
├── redis-check-aof
├── redis-check-rdb
├── redis-cli
├── redis-sentinel -> redis-server
└── redis-server
1 directory, 6 files
ln 软连接和硬链接
软连接就像windows的快捷方式
复制 创建软连接
ln -s file file.s 给文件file创建软连接file.s
对file文件执行操作与对file.s软连接文件执行操作,效果相同。
Linux下的软链接行为和windows下的快捷方式差不多,但是如果是用相对路径创建的软链接,在软链接移动之后就会失效,无法访问。这一点和windows快捷方式不同,windows快捷方式随便放哪里都行。
所以在创建软连接是,需要使用绝对路径,这样对软连接移动不会失效
复制 ln -s /home/test/file file.s
软连接的权限,默认为 rwxrwxrwx
软连接的权限仅仅代表软连接文件的权限,默认是所有权限,文件权限的控制最终仍然由源文件决定。
硬链接就是同步文件,对硬链接修改,源文件与与源文件相关的硬链接都会被同步修改, 这是因为他们拥有相同的Inode节点。
复制 ln file file.h
ln file file.h2
对file file.h file.h2 这三个文件任意一个修改,其他两个都会被修改
---- 他们的Inode相同,都是4096
[root@s1 ~]# stat file
文件:"file"
大小:6 块:8 IO 块:4096 普通文件
设备:fd00h/64768d Inode:8410242 硬链接:3
权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
最近访问:2021-02-12 20:33:01.486864859 +0800
最近更改:2021-02-12 20:33:01.486864859 +0800
最近改动:2021-02-12 20:33:20.100281327 +0800
创建时间:-
[root@s1 ~]# stat file.h
文件:"file.h"
大小:6 块:8 IO 块:4096 普通文件
设备:fd00h/64768d Inode:8410242 硬链接:3
权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
最近访问:2021-02-12 20:33:01.486864859 +0800
最近更改:2021-02-12 20:33:01.486864859 +0800
最近改动:2021-02-12 20:33:20.100281327 +0800
创建时间:-
硬链接计数器,为3,代表有三个硬链接,当删除一个计数会减一,如果变为0才会删除文件
ls -l
-rw-r--r-- 3 root root 6 2月 12 20:33 file
-rw-r--r-- 3 root root 6 2月 12 20:33 file.h
-rw-r--r-- 3 root root 6 2月 12 20:33 file.h2
用户与用户组
whoami 查看当前登录用户
复制 [root@s1 ~]# whoami
root
adduser 添加用户
passwd 修改密码
addgroup 添加用户组
su 切换用户
deluser 删除用户
delgroup 删除用户组
chmod 修改权限
change mode
复制 ls -l
-rw-r--r-- 3 root root 6 2月 12 20:33 file
-rw-r--r-- 3 root root 6 2月 12 20:33 file.h
-rw-r--r-- 3 root root 6 2月 12 20:33 file.h2
第一栏
第2、3、4 分别代表用户的读、写、执行三个权限的状态,如果是 – ,则代表没有该权限
第5、6、7分别代表所有者所在的用户组的读、写、执行三个权限的状态
第8、9、10分别代表其他用户的读、写、执行三个权限的状态
第三栏:
第四栏:
文字设定法
复制 chmod [who] [+|-|=] [mode] filename
操作对象who可以是下述字母中的任一个或者它们的组合
u 表示”用户(user)”,即文件或目录的所有者
g 表示”同组(group)用户”,即与文件所有者有相同组ID的所有用户
o 表示”其他(others)用户”
a 表示”所有(all)用户”,它是系统默认值
作符号可以是:
+ 添加某个权限
-取消某个权限
= 赋予给定权限并取消其他所有权限(如果有的话)
给用户添加该文件的执行权限
chmod u+x file
数字设定法
复制 chmod 操作码 filename 直接用操作码修改文件权限
-rw-rw-r—
421421421
三个组的权限都用二进制编号,比如要设置当前用户对文件的读写和执行权限,则当前用户的操作权限为
所以,
复制 chomd 777 file
其中三个数字分别代表 所有者、所有者所在组、其他用户 的权限,777代表所有用户都有读写执行权限
chown 更改文件所有者
change own:
复制 chown username filename 修改文件所有者
同时修改文件所有用户与用户组:
复制 chown username:groupname filename
chgrp 修改文件所属组
复制 chgrp groupname filename 修改文件所有组
查找
wich 查看指定命令所在路径
复制 [root@s1 ~]# which ls
alias ls='ls --color=auto'
/usr/bin/ls
whereis 查询安装的命令信息
用于搜索命令所在的路径以及帮助文档所在的位置,不能搜索用户自己创建的文件等信息。
不能看到shell命令(自带的命令,比如cd命令),只能看到外部安装的命令(环境变量中的命令)
find 文件搜索
find [搜索范围] [搜索条件]
按照文件类型搜索
-type
按文件类型搜索 d/p/s/c/b/l/f:文件
按照文件名搜索
复制 find ./ -name "*file*.jpg" 搜索所有的jpg文件
*匹配任意内容
?匹配任意一个字符
[]匹配任意一个括号内的字符
指定搜索深度
应作为第一个参数出现
复制 find ./ -maxdepth 1 -name "*file*.jpg"
指定文件大小范围
按文件大小搜索. 单位:b(512字节,是一个磁盘块)、c(bytes)、w(two bytes)、k、M、G
复制 find /home/ -size +20M -size -50M 找寻大于20M 小于50M的文件
指定时间范围
操作标志:
a 访问时间(access time),指的是文件最后被读取的时间,可以使用touch命令更改为当前时间;
c 变更时间(change time),指的是文件本身最后被变更的时间,变更动作可以使chmod、chgrp、mv等等;
m 修改时间(modify time),指的是文件内容最后被修改的时间,修改动作可以使echo重定向、vi等等;
时间标志:
复制 find /home/ -ctime 1 查找一天以内被修改的文件
find /home/ -amin 1 查找一分钟内被访问的文件
搜索结果并执行 -exec
将find搜索的结果集执行某一指定命令
复制 find /usr/ -name '*tmp*' -exec rm -rf {} \;
搜索完毕后,对这些命令执行rm -rf 搜索的文件名称
,将删除含有这些文件
搜索结果并执行 交互方式 -ok
在执行删除之前会提示是否删除
复制 find /usr/ -name '*tmp*' -ok rm -rf {} \;
grep 寻找文件内容
复制 grep -r 'copy' ./ -n 递归搜寻当前目录下的所有文件内容中包含copy字符的信息
-n 展示行号
-r 递归寻找
grep "test combinations" ./ -rn
./redis-6.0.10/deps/jemalloc/scripts/gen_travis.py:29:# instead, we only test combinations of up to 2 'unusual' settings, under the
使用管道搜索结果集:
xargs 结果集分段处理
复制 find … | xargs ls -l 对find操作的结果集进行操作
等价于
find … -exec ls -l {} \;
两者差别在于当结果集合很大的时候,xargs会对结果进行分片处理(分为多个片段 一个个处理),所以性能好些。但xargs也有缺陷,xargs默认用空格来分割结果集,当文件名有空格的时候,会因为文件名被切割失效。
解决xargs问题:
因为xargs对结果集默认使用空格拆分,所以我们可以使用别的作为拆分依据,就不会出现这类问题,这里使用0作为拆分依据:
复制 find /usr/ -name '*tmp*' -print0 | xargs -print0 ls -l
软件包管理
debian分支 apt-get
复制 安装软件
sudo apt-get install softname
更新软件列表
sudo apt-get update
卸载软件
sudo apt-get remove softname
debian分支 deb包
复制 安装deb软件包
sudo dpkg -i xxx.deb
删除软件包:
sudo dpkg -r xxx.deb
连同配置文件一起删除:
sudo dpkg -r --purge xxx.deb
查看软件包信息命令:
sudo dpkg -info xxx.deb
查看文件拷贝详情命令:
sudo dpkg -L xxx.deb
查看系统中已经安装软件包信息命令:
sudo dpkg -l
重新配置软件包命令:
sudo dpkg-reconfigure xxx
rpm包管理
复制 安装一个包:
rpm -ivh package_name
查询已安装的软件包:
rpm -q 已安装的软件包名称
模糊查询已安装的软件包名称:
rpm -qa | grep 字符串
查询某一个已经安装的软件的所有相关目录以及文件:
rpm -ql 软件名
列出该软件的所有配置文件:
rpm -qc 软件名
查询某个软件的依赖的其他软件:
rpm -qR 软件名
查询文件属于哪个软件包:
rpm -qf 文件名
卸载软件包:
rpm -e --nodeps package_name
列出 该软件被修改过的配置文件:
rpm -V 已安装的软件名
列出文件是否被改动:
rpm -Vf 文件名
将软件回退到低版本:
rpm --Uvh --oldpackage --nodeps package_name
备份已经安装在环境的的软件:
rpmrebuild pacakge_name
#如果不需要此询问 可以使用:
rpmrebuild -b
参考: https://www.linuxprobe.com/rpm-redhat.html
打包和压缩
tar 打包
复制 打包 将etc目录打包为一个etc.tar的包文件,不压缩 -c create 创建一个包文件 -v view 显示过程 -f file 指定包名称以及位置
tar -cvf /tmp/etc.tar /etc
将多个文件打包
tar -cvf /tmp/test.tar file1 file2 file3
解包
tar -xvf /tmp/etc.tar
gzip 压缩解压
复制 将文件filename压缩为filename.gz
gzip filename
解压文件
gunzip filename.gz
gzip 不支持压缩多个文件以及文件夹
tar 打包并压缩
因为gzip 以及 bzip2都是只能对单个文件进行压缩,一来不能压目录,二来不能打包。所以通常使用tar和压缩命令配合的方式压缩文件:
复制 tar打包目录并使用gizp压缩
tar -zcvf /tmp/etc.tar.gz /etc
解压.tar.gz
tar -zxvf /tmp/etc.tar.gz
tar打包并使用bzip2压缩
tar -jcvf /tmp/etc.tar.gz /etc
解压.tar.gz
tar -jcvf /tmp/etc.tar.gz /etc
zip 压缩
复制 zip -r test.zip dir1 dir2 file1 将dir1 dir2 目录和文件 file1压缩为test.zip,后面可以添加多个压缩材料
unzip test.zip
rar 压缩
复制 rar a -r newdir.rar dir1 dir2 file1 将dir1 dir2 目录和文件 file1压缩为newdir.rar,后面可以添加多个压缩材料
unrar x newdir.rar
进程管理
who 查看当前线上和用户情况
复制 [root@s1 tmp]# who
root tty1 2021-02-12 00:09
root pts/0 2021-02-12 12:13 (192.168.1.52)
ps 查看进程
查看与当前用户交互的进程
复制 [root@s1 ~]# ps
PID TTY TIME CMD
7469 pts/0 00:00:00 bash
7733 pts/0 00:00:00 ps
查看所有进程的详细信息
复制 ps aux
ps -ef
ps aux | grep redis 查看reids的进程信息
jobs 查看当前shell下后台运行的作业
复制 cat& (后台运行cat命令)
[root@s1 tmp]# jobs (查看当前用户作业)
[1]- 已停止 cat
[2]+ 已停止 cat
kill 杀死进程
env 查看当前进程的环境变量
top 文字版任务管理器
网络管理
ifconfig
ping
netstat
常用服务器构建
ftp
nfs
ssh
scp
telnet
其他命令
man 帮助命令y
共有七章帮助分别为:
复制 man 章节 查询内容
man 1 ls
man 2 read
alias 命令别名
ll 命令是ls命令的别名,就是通过alias命令设置的:
查看当前系统中已经设置的别名:
复制 [root@s1 tmp]# alias
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
date 显示系统时间
复制 [root@s1 tmp]# date
2021年 02月 12日 星期五 23:35:13 CST
vi
三种工作模式
进入编辑模式
光标移动
左下上右
gcc
安装
ubuntu:
复制 apt install build-essential
编译4个步骤
预处理,生成预编译文件(.i文件), 展开宏、头文件,替换条件编译,删除注释、空行、空白
gcc –E hello.c –o hello.i
编译,生成汇编代码文件 (.s文件),消耗时间、系统资源最多
gcc –S hello.i –o hello.s
汇编,生成目标文件(.o文件),将汇编指令翻译成机器指令
gcc –c hello.s –o hello.o
链接,生成可执行文件,数据段合并、地址回填
gcc hello.o –o hello
复制 #include <stdio.h>
// hello
int main(){
printf("Hello World!\n");
return 0;
}
gcc常用参数
静态库与共享库
静态库
静态库在文件中静态展开,所以有多少文件就展开多少次,非常吃内存,100M展开100次,就是1G,但是这样的好处就是静态加载的速度快
共享库又称为动态库,使用动态库会将动态库加载到内存,10个文件也只需要加载一次,然后这些文件用到库的时候临时去加载,速度慢一些,但是很省内存。
静态库制作以及使用
静态库名字以lib开头,以.a结尾。例如: libmylib.a
。静态库生成指令:
复制 gcc -c add.c -o add.o
gcc -c add.c -o sub.o
gcc -c add.c -o div1.o
# 将add.o sub.o div1.o生成为静态库 libmylib.a
ar rcs libmymath.a add.o sub.o div1.o
# 通过lib参数指定静态库
gcc test.c lib libmymath.a -o test
# 这里编译会出现警告,因为直接使用mymath库中的函数,没有引入头文件
# 在编译时,编译器遇到一个函数,必须需要函数定义,如果没有函数定义,则必须要有函数声明
# 如果都没有,编译器会帮你进行隐式声明,但是会抛出警告信息
ysx@DESKTOP-3BPMFCB:/mnt/c/c$ gcc test.c ./libmymath.a -o test
test.c: In function ‘main’:
test.c:6:32: warning: implicit declaration of function ‘add’ [-Wimplicit-function-declaration]
6 | printf("%d+%d=%d\n", a, b, add(a, b));
| ^~~
test.c:7:32: warning: implicit declaration of function ‘sub’ [-Wimplicit-function-declaration]
7 | printf("%d-%d=%d\n", a, b, sub(a, b));
| ^~~
test.c:8:32: warning: implicit declaration of function ‘div1’ [-Wimplicit-function-declaration]
8 | printf("%d/%d=%d\n", a, b, div1(a, b));
| ^~~~
# 执行test可执行程序
./test
100+10=110
100-10=90
100/10=10
add.c
:
复制 int add(int a, int b)
{
return a + b;
}
sub.c
:
复制 int sub(int a, int b)
{
return a - b;
}
div1.c
:
复制 int div1(int a, int b)
{
return a / b;
}
test.c
:
复制 #include <stdio.h>
int main()
{
int a = 100;
int b = 10;
printf("%d+%d=%d\n", a, b, add(a, b));
printf("%d-%d=%d\n", a, b, sub(a, b));
printf("%d/%d=%d\n", a, b, div1(a, b));
return 0;
}
添加隐式声明
复制 #include <stdio.h>
int add(int, int);
int sub(int, int);
int div1(int, int);
int main()
{
int a = 100;
int b = 10;
printf("%d+%d=%d\n", a, b, add(a, b));
printf("%d-%d=%d\n", a, b, sub(a, b));
printf("%d/%d=%d\n", a, b, div1(a, b));
return 0;
}
再次编译,就不会报错了。这种方法缺点很明显,需要库的使用者知道库里的函数,并且需要将定义一个个加入到代码中。
静态库头文件
在日常开发中,库一般是从网络上down下来的。我们没法知道库中的函数。所以通常在每个库中会额外包含一个头文件,在头文件中生命了这个库中所有函数的隐式声明。
定义mymath.h:
复制 #ifndef _MYMATH_H_
#define _MYMATH_H_
int add(int, int);
int sub(int, int);
int div1(int, int);
#endif
ifndef 用来防止头文件多次展开,如果第一次include没有 _MYMATH_H_
宏定义,会展开,下次会通过ifdef判断是否有 ,如果有了不会再次展开。
在test.c中引入头文件:
复制 #include <stdio.h>
#include <mymath.h>
int main()
{
int a = 100;
int b = 10;
printf("%d+%d=%d\n", a, b, add(a, b));
printf("%d-%d=%d\n", a, b, sub(a, b));
printf("%d/%d=%d\n", a, b, div1(a, b));
return 0;
}
执行gcc编译链接,使用-I指定头文件路径:
复制 gcc test.c libmymath.a -o test -I ./ -Wall
动态库(共享库)
当调用到动态库函数,才会去加载。
写在源代码里的函数,他们的地址相对main函数偏移是一定的,链接时,回填main函数地址之后,其他源代码里的函数也就得到了地址,从而可以通过地址调用函数
而动态库里的函数在链接阶段无法确定,所以动态库中的函数会用一个@plt来标识,当动态库加载到内存时,再用加载进去的地址将@plt替换掉。
制作动态库
生成位置无关的.o文件,使用下面这个参数过后,生成的函数就和位置无关,挂上@plt标识,等待动态绑定
复制 gcc -c add.c -o add.o -fPIC
gcc -c sub.c -o sub.o -fPIC
gcc -c div1.c -o div1.o -fPIC
使用 gcc -shared制作动态库
复制 # gcc -shared -o lib库名.so add.o sub.o div1.o
gcc -shared -o libmymath.so add.o sub.o div1.o
编译可执行程序时指定所使用的动态库: -L
:指定库路径 -l
:指定库名(注意,如果动态库文件为libmymath.so,那么库名称为 mymath)
复制 gcc test.c -o test -l mymath -L ./ -I ./
执行含有动态库的程序
执行 ./test
报错:
复制 ./test: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
提示,找不到动态库文件。这是因为动态链接器找不到动态库的位置,而不是了链接器(-L 与 -l 是为链接器指定动态库的位置,发生在链接阶段):
连接器: 工作于链接阶段,工作时需要 -l 和 -L
动态链接器: 工作于程序运行阶段,工作时需要提供动态库所在目录位置
动态库加载错误:通过环境变量指定动态库位置
复制 export LD_LIBRARY_PATH=动态库路径
export LD_LIBRARY_PATH=./
可以通过.bashrc永久生效
动态库加载错误:拷贝动态库到 /lib 路径
Linux的 /lib
路径,是标准C库的所在位置
动态库加载错误:/etc/ld.so.conf 配置库路径
复制 sudo vi /etc/ld.so.conf
写入 动态库绝对路径
sudo ldconfig -v 让配置文件生效
gdb调试工具
TODO,对我来说不重要
makefile项目管理
用途
基本规则
makefile就像一个脚本,通过执行 make
命令,执行项目根路径下的 Makefile
或者 makefile
脚本文件。
一个规则 :
makefile的以来是从上到下的,换句话说就是目标文件是第一句里的目标,如果不满足执行以来就会继续向下执行。如果满足了生成目标的依赖,就不会继续向下执行了。make会自动寻找规则里需要的材料文件,执行规则下面的行为生成规则中的目标。
复制 # 目标是生成hello文件,依赖是hello.o文件,生成命令为 gcc hello.o -o hello. 如果没有hello.o 会向下执行
hello:hello.o
gcc hello.o -o hello
# 目标是生成hello.o文件,以来是hello.c文件,生成命令为 gcc -c hello.c -o hello.o
gcc -c hello.c -o hello.o
多文件联合编译:
复制 test:test.c add.c sub.c div1.c
gcc test.c add.c sub.c div1.c -o test
上面这种编译方式,如果 add.c 有更改,其他c文件也要重新编译,C语言的编译速度很慢,可以改造为多个目标:
复制 test:test.o add.o sub.o div1.o
gcc test.o add.o sub.o div1.o -o test
test.o:test.c
gcc -c test.c -o test.o
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
注意,上面的方式,test目标必须放在首位,因为makefile以文件中的第一个目标为终极目标,如果想指定终极目标可以使用ALL:
复制 ALL:test
test:test.o add.o sub.o div1.o
gcc test.o add.o sub.o div1.o -o test
test.o:test.c
gcc -c test.c -o test.o
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
若想生成目标,检查规则中的依赖条件是否存在,则寻找是否有规则用来生成该以来文件
检查规则中的目标是否需要更新,必须先检查他的所有依赖,原来中有人一个被更新,则目标必须更新
如果目标文件不依赖任何条件,则执行对应命令,以示更新
两个函数:
复制 src = $(wilcard *.c); #找到目录下的所有c文件,赋值给src, src == add.c div1.c sub.c test.c
obj = $(patsubst %.c,%.o,$(src)) #将src变量中的所有后缀为.c的文件,替换为.o obj == add.o div1.o sub.o test.o
复制 src = $(wilcard ./*.c);
obj = $(patsubst ./%.c,./%.o,$(src))
ALL:test
test:$(obj)
gcc $(obj) -o test
test.o:test.c
gcc -c test.c -o test.o
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
clean:
clean只有目标,没有依赖,在命令前增加 -
,可以避免报错停止
复制 src = $(wilcard ./*.c);
obj = $(patsubst ./%.c,./%.o,$(src))
ALL:test
test:$(obj)
gcc $(obj) -o test
test.o:test.c
gcc -c test.c -o test.o
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
div1.o:div1.c
gcc -c div1.c -o div1.o
clean:
-rm -rf $(obj) test
三个自动变量:
$<
:在规则命令中,表示规则中的第一个条件,如果将该变量用在模式规则中,它可以将依赖条件列表中的依赖依次取出,套用模式规则
$^
:在规则命令中,表示规则中的所有条件,组成一个列表,以空格隔开,如果这个列表中有重复项,则去重
使用三个变量修改makefile文件:
复制 src = $(wilcard ./*.c);
obj = $(patsubst ./%.c,./%.o,$(src))
ALL:test
test:$(obj)
gcc $^ -o $@
test.o:test.c
gcc -c $< c -o $@
add.o:add.c
gcc -c $< -o $@
sub.o:sub.c
gcc -c $< -o $@
div1.o:div1.c
gcc -c $< -o $@
clean:
-rm -rf $(obj) test
规则模式:
上面的makefile的规则模式为:
复制 %.o:%.c
gcc -c $< -o $@
更改为规则模式为:
复制 src = $(wilcard ./*.c);
obj = $(patsubst ./%.c,./%.o,$(src))
ALL:test
test:$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $< c -o $@
clean:
-rm -rf $(obj) test
一个小型makefile项目的基本结构
复制 myproject/
include/ 公共头文件,放在外面的原因是这部分头文件,具有部分接口的性质
src/ 源代码目录
publib1/ 公共库1
Makefile
pub1.h
pub1.cpp
....cpp
publib2/ 公共库2
...
busi1/ 业务模块1
busi2/ 业务模块2
...
main.cpp 系统主程序
pubmake 公共makefile 后面会讲述其结构
Makefile 系统编译makefile
README ReadMe文档,后面会讲述其结构
lib/ 库目录
bin/ 可执行文件目录
cfg/ 配置文件目录
res/ 资源目录
系统调用
文件IO
文件描述符
文件描述符是指向一个文件结构体(file_struct)的指针。PCB 进程控制模块为每一个进程维护一个文件描述符表,这个表最大长度为1024个,也就是说,每个进程最多可以打开1024个文件,其中有三个文件描述符 0 ,1 ,2 默认打开:
并且,进程新打开的文件描述,整数值总是最小的。
open/close
复制 int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
作用: 以各种方式打开文件
返回值: 返回打开的文件句柄,-1 打开失败
函数说明 参数pathname 指向欲打开的文件路径字符串 ,
既可以是相对路径也可以是绝对路径。 flags
参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,所以这些常数的宏定义都以 O_
开头,表示or
下列是参数flags 所能使用的旗标:
必选项:以下三个常数中必须指定一个,且仅允许指定一个。
O_RDWR 以可读写方式打开文件。上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用OR(|)运算符组合。
以下可选项可以同时指定0个或多个,和必选项按位或起来作为flag参数。
O_CREAT 若欲打开的文件不存在则自动建立该文件。
O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。
O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。
O_TRUNC 若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失。
O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。
O_DIRECTORY 如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。
第三个参数mode指定文件权限 ,可以用八进制数表示,比如0644表示-rw-r–r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,参数mode 则有下列数种组合,只有在建立新文件时才会生效,文件权限由open的mode参数和当前进程的umask掩码共同决定,因此该文件权限应该为(mode-umaks)。
S_IRWXU00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
S_IRUSR 或S_IREAD,00400权限,代表该文件所有者具有可读取的权限。
S_IWUSR 或S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。
S_IXUSR 或S_IEXEC,00100 权限,代表该文件所有者具有可执行的权限。
S_IRWXG 00070权限,代表该文件用户组具有可读、可写及可执行的权限。
S_IRGRP 00040 权限,代表该文件用户组具有可读的权限。
S_IWGRP 00020权限,代表该文件用户组具有可写入的权限。
S_IXGRP 00010 权限,代表该文件用户组具有可执行的权限。
S_IRWXO 00007权限,代表其他用户具有可读、可写及可执行的权限。
S_IROTH 00004 权限,代表其他用户具有可读的权限
S_IWOTH 00002权限,代表其他用户具有可写入的权限。
S_IXOTH 00001 权限,代表其他用户具有可执行的权限。
mkdir
复制 #include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int mkdirat(int dirfd, const char *pathname, mode_t mode);
read/write
复制 #include <unistd.h>
// fd:文件描述符, buf:存数据的缓冲区, count:缓冲区大小 return 成功读取的字节数,如果为0说明到达文件结尾
ssize_t read(int fd, void *buf, size_t count);
// fd:文件描述符, buf:待写出数据的缓冲区. count:要写入的字节大小 return 写入的字节数
ssize_t write(int fd, const void *buf, size_t count);
拷贝演示:
复制 char *target = "/tmp/cp2.txt";
int rFd = open(source, O_RDONLY);
int wFd2 = open(target, O_WRONLY | O_CREAT, 0666);
char buf[1024];
int n = 0;
while ((n = read(rFd, buf, 1024)) != 0) {
write(wFd2, buf, n);
}
close(rFd);
close(wFd2);
阻塞和非阻塞
阻塞是文件(设备文件、网络文件)的属性。
读取常规文件不会发生阻塞,不管读多少字节,read一定会在有限的时间内回应。从终端设备 或者网络读取则不一定。如果从终端输入的数据没有换行符,调用read读取终端设备就会发生阻塞,比较常见的就是一直等待输入。如果网络上没有收到数据包,调用read读取网络就会发生阻塞,并且阻塞的时间也是不确定的。
复制 #include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// read阻塞调用
int main(void)
{
char buf[10];
int n;
n = read(STDIN_FILENO, buf, 10); // #define STDIN_FILENO 0 STDOUT_FILENO 1 STDERR_FILENO 2
if(n < 0){
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0;
}
复制 #include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// read 非阻塞调用, read函数将会立即返回,如果errorno为EAGAIN代表正在非阻塞处理,使用while循环检查
int main(void)
{
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if (fd < 0) {
perror("open /dev/tty");
exit(1);
}
tryagain:
n = read(fd, buf, 10);
if (n < 0) {
if (errno != EAGAIN) { // if(errno != EWOULDBLOCK)
perror("read /dev/tty");
exit(1);
} else {
write(STDOUT_FILENO, "try again\n", strlen("try again\n"));
sleep(2);
goto tryagain;
}
}
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
复制 #include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "time out\n"
// read非阻塞调用,超时处理
int main(void)
{
char buf[10];
int fd, n, i;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd < 0){
perror("open /dev/tty");
exit(1);
}
printf("open /dev/tty ok... %d\n", fd);
for (i = 0; i < 5; i++){
n = read(fd, buf, 10);
if (n > 0) { //说明读到了东西
break;
}
if (errno != EAGAIN) { //EWOULDBLOCK
perror("read /dev/tty");
exit(1);
} else {
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
sleep(2);
}
}
if (i == 5) {
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
} else {
write(STDOUT_FILENO, buf, n);
}
close(fd);
return 0;
}
fcntl
fcntl用来改变一个【已经打开】的文件的 访问控制属性, 获取文件状态: F_GETFL
,设置文件状态: F_SETFL
复制 int (int fd, int cmd, ...)
// fd 文件描述符 cmd 决定了后续参数个数
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK; // 通过位图的方式增加flag
fcntl(fd, F_SETFL, flgs);
lseek
更改文件读写位置。
复制 off_t lseek(int fd, off_t offset, int whence);
// fd:文件描述符
// offset: 偏移量,就是将读写指针从whence指定位置向后偏移offset个单位
// whence:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END
// 返回值,成功:较起始位置偏移量 失败:-1 errno
应用场景:
文件的“读”、“写”使用同一偏移位置。(读到100偏移位置,紧接着调用write写,是从101写)
使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作。
使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);
目录项和inode
一个文件主要由两部分组成,dentry(目录项)和inode:
inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置…也叫做文件属性管理结构
大多数的inode都存储在磁盘上。少量常用、近期使用的inode会被缓存到内存中。
所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。