Linux系统编程

学习课程:

基础部分

Shell

命令解释器,格局输入的命令执行相应的命令。查询当前操作系统有哪些shell:

cat /etc/shells .s

查询当前系统正在使用的shell:

echo $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-d
HomeCtrl-afirst letter
EndCtrl-eend
BackspaceBackspacedelete光标前面的单个字符
清除整行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(变量) 的缩写,这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件

相对路径与绝对路径

从根 / 目录开始的路径名为绝对路径,如:

cd /home
ls /usr

从当前位置开始描述的路径为相对路径:

cd ../../
ls abc/efg

. 和 ..

. 当前目录
.. 当前目录的上一级目录
/ 根目录

linux八种文件类型

  1. 普通文件: –
  2. 目录文件:d
  3. 字符设备文件:c
  4. 块设备文件:b
  5. 软连接:l
  6. 管道文件:p
  7. 套接字:s
  8. 未知文件

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 创建空文件

touch filename

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 添加用户

adduser lisi

passwd 修改密码

passwd username

addgroup 添加用户组

addgroup test

su 切换用户

su lisi

deluser 删除用户

deluser username

delgroup 删除用户组

delgroup groupname

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

第一栏

  • 第1位代表普通文件
  • 第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

三个组的权限都用二进制编号,比如要设置当前用户对文件的读写和执行权限,则当前用户的操作权限为

  • 4(读)
  • 2(写)
  • 1(执行)

所以,

  • 6 代表角色拥有读写权限
  • 5代表角色拥有读取和执行权限
  • 3代表角色拥有写入和执行权限
  • 7代表拥有所有的权限
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 查询安装的命令信息

  1. 用于搜索命令所在的路径以及帮助文档所在的位置,不能搜索用户自己创建的文件等信息。
  2. 不能看到shell命令(自带的命令,比如cd命令),只能看到外部安装的命令(环境变量中的命令)
  • -b:只查找可执行文件
  • -m:只查找帮助文件

find 文件搜索

find [搜索范围] [搜索条件]

按照文件类型搜索

-type 按文件类型搜索 d/p/s/c/b/l/f:文件

find ./ -type d 搜索所有目录
按照文件名搜索
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等等;

时间标志:

  • time 天
  • min分钟
find /home/ -ctime 1 查找一天以内被修改的文件
find /home/ -amin 1 查找一分钟内被访问的文件
搜索结果并执行 -exec

将find搜索的结果集执行某一指定命令

find /usr/ -name '*tmp*' -exec rm -rf {} \;
  • 搜索 /usr/ 路径下名称含有tmp的文件
  • 搜索完毕后,对这些命令执行 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

使用管道搜索结果集:

ps aux | grep redis

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和压缩命令配合的方式压缩文件:

  1. 先将文件使用tar打包
  2. 在使用压缩命令压缩
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

共有七章帮助分别为:

  1. 可执行程序或者Shell
  2. 系统调用(内核提供的函数)
  3. 库调用(程序库中的函数)
  4. 特殊文件(通常位于 /dev)
  5. 文件格式规范,如 /etc/passwd
  6. 游戏
  7. 杂项(包括宏包和规范)
  8. 系统管理命令 (通常只针对root用户)
  9. 内核历程[非标准
man 章节 查询内容
man 1 ls
man 2 read

alias 命令别名

ll 命令是ls命令的别名,就是通过alias命令设置的:

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

三种工作模式

image-20210212234015232

进入编辑模式

  • i 进入编辑模式,光标前插入字符
  • a 进入编辑模式,光标后插入字符
  • o 进入编辑模式,光标所在行的下一行插入
  • I 进入编辑模式,光标所在行的行首插入
  • A 进入编辑模式,光标所在行的行末插入字符
  • O 进入编辑模式,光标所在行的上一行插入字符
  • s 删除光标所在字符并进入编辑模式
  • S 删除光标所在行并进入编辑模式

光标移动

  • h 左移
  • j 下移
  • k 上移
  • l 右移

左下上右

gcc

安装

ubuntu:

apt install build-essential

编译4个步骤

  1. 预处理,生成预编译文件(.i文件), 展开宏、头文件,替换条件编译,删除注释、空行、空白

    gcc –E hello.c –o hello.i

  2. 编译,生成汇编代码文件 (.s文件),消耗时间、系统资源最多

    gcc –S hello.i –o hello.s

  3. 汇编,生成目标文件(.o文件),将汇编指令翻译成机器指令

    gcc –c hello.s –o hello.o

  4. 链接,生成可执行文件,数据段合并、地址回填

    gcc hello.o –o hello

#include <stdio.h>
// hello
int main(){
        printf("Hello World!\n");
        return 0;
}

gcc常用参数

  • -I,指定头文件所在的位置
  • -g,编译时添加调试语句,增加gdb调试支持
  • -Wall,显示所有警告信息
  • -D,向程序中动态注册宏定义

静态库与共享库

静态库

静态库在文件中静态展开,所以有多少文件就展开多少次,非常吃内存,100M展开100次,就是1G,但是这样的好处就是静态加载的速度快

image-20210214091041291

共享库又称为动态库,使用动态库会将动态库加载到内存,10个文件也只需要加载一次,然后这些文件用到库的时候临时去加载,速度慢一些,但是很省内存。

image-20210214091346889
静态库制作以及使用

静态库名字以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

动态库(共享库)

当调用到动态库函数,才会去加载。

image-20210214132605686
  • 写在源代码里的函数,他们的地址相对main函数偏移是一定的,链接时,回填main函数地址之后,其他源代码里的函数也就得到了地址,从而可以通过地址调用函数
  • 而动态库里的函数在链接阶段无法确定,所以动态库中的函数会用一个@plt来标识,当动态库加载到内存时,再用加载进去的地址将@plt替换掉。
制作动态库
  1. 生成位置无关的.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
  2. 使用 gcc -shared制作动态库

    # gcc -shared -o lib库名.so add.o sub.o div1.o
    gcc -shared -o libmymath.so add.o sub.o div1.o
  3. 编译可执行程序时指定所使用的动态库:-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项目管理

用途

  • 项目代码编译管理
  • 节省编译项目时间
  • 一次编写终身受益

基本规则

  1. 1规则
  2. 2个函数
  3. 3个自动变量

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
  1. 若想生成目标,检查规则中的依赖条件是否存在,则寻找是否有规则用来生成该以来文件
  2. 检查规则中的目标是否需要更新,必须先检查他的所有依赖,原来中有人一个被更新,则目标必须更新
    • 分析各个目标和依赖之间的关系
    • 根据依赖关系子弟向下执行命令
    • 根据修改时间比目标新,确定更新
    • 如果目标文件不依赖任何条件,则执行对应命令,以示更新

两个函数:

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

文件描述符

image-20210216161147293

文件描述符是指向一个文件结构体(file_struct)的指针。PCB 进程控制模块为每一个进程维护一个文件描述符表,这个表最大长度为1024个,也就是说,每个进程最多可以打开1024个文件,其中有三个文件描述符 0 ,1 ,2 默认打开:

  • 0 – STDIN_FILENO 标准输入
  • 1 – STDOUT_FILENO 标准输出
  • 2 – STDERR_FILENO 错误输出

并且,进程新打开的文件描述,整数值总是最小的。

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_RDONLY 以只读方式打开文件
  • O_WRONLY 以只写方式打开文件
  • 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_NDELAY 同O_NONBLOCK。
  • O_SYNC 以同步的方式打开文件。
  • 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

应用场景:

  1. 文件的“读”、“写”使用同一偏移位置。(读到100偏移位置,紧接着调用write写,是从101写)
  2. 使用lseek获取文件大小
  3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作。
  4. 使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);

目录项和inode

image-20210216172434152

一个文件主要由两部分组成,dentry(目录项)和inode:

  • inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置…也叫做文件属性管理结构
  • 大多数的inode都存储在磁盘上。少量常用、近期使用的inode会被缓存到内存中。
  • 所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值