Linux知识点

文章目录

1. Linux下基本指令

1.0 / * . … > >> |

  1. /(斜线):

    • 在文件系统路径中,/ 是路径分隔符,用于分隔目录名。例如,/home/user/Documents 表示 Documents 目录位于 user 用户的 home 目录下。
    • 在根目录表示方式中,/ 表示根目录,即文件系统的顶层目录。所有其他目录和文件都是从根目录开始的。
    • 在一些特定的命令中,如 cd /,它表示切换到根目录。
  2. *****(星号):

    • * 是一个通配符,用于匹配任意数量(包括零个)的字符。
    • 在文件名中,* 可以匹配零个或多个字符。例如,*.txt 可以匹配所有以 .txt 结尾的文件名。
    • 在命令行中,* 可以用于批量操作文件。例如,rm *.txt 将会删除当前目录下所有以 .txt 结尾的文件。
  3. .(点):

    • 在文件系统中,. 表示当前目录。例如,./script.sh 表示在当前目录下找到并执行名为 script.sh 的脚本文件。
    • 在正则表达式中,. 表示匹配任意单个字符。
  4. (点点):

    • .. 表示父目录,即当前目录的上一级目录。
    • 在文件系统中,.. 用于访问当前目录的父目录。例如,cd .. 表示切换到当前目录的父目录。
    • 在一些路径操作中,.. 用于表示相对路径中的父目录。例如,../folder 表示当前目录的父目录中的 folder 目录。
  5. 输出重定向 (>):使用单个大于符号 > 可以将命令的输出写入到指定的文件中。如果目标文件不存在,则会创建该文件;如果目标文件已经存在,那么会覆盖原有内容,将新的输出写入其中。

    ls > file.txt
    
  6. 追加重定向 (>>):使用双大于符号 >> 可以将命令的输出追加到指定的文件中。如果目标文件不存在,则会创建该文件;如果目标文件已经存在,则会在文件末尾添加新的输出内容,而不会覆盖原有内容。

    例如:

    echo "Hello, World!" >> file.txt
    

    上面的命令将 “Hello, World!” 字符串追加到名为 “file.txt” 的文件末尾。如果 “file.txt” 不存在,则会创建该文件并写入字符串;如果 “file.txt” 已经存在,则会在文件末尾追加该字符串。

  7. 管道符号 | 是一种特殊的命令操作符,用于将一个命令的标准输出(stdout)连接到另一个命令的标准输入(stdin),从而创建一个管道。

    command1 | command2
    

管道的功能和用途:

  1. 连接命令:管道可以将多个命令连接在一起,使其形成一个命令链。这样可以将多个简单的命令组合起来,以实现更复杂的功能。
  2. 数据传输:管道使得一个命令的输出可以直接传递给另一个命令,而不需要将数据存储到文件中。这样可以提高效率并减少临时文件的使用。
  3. 数据处理:管道使得可以对输出进行实时处理,通过连接多个命令,可以对数据进行过滤、排序、统计等操作。
  4. 实时监控:通过管道,可以实现实时监控某个命令的输出,并对其进行相应的处理或过滤,例如使用 tail -f 命令实时监控日志文件的变化。

示例:

  • ls 命令的输出传递给 grep 命令,以过滤特定的文件或目录:

    ls | grep keyword
    
  • ps 命令的输出传递给 grepwc 命令,以统计特定进程的数量:

    ps aux | grep process_name | wc -l
    
  • cat 命令的输出传递给 sed 命令,以对文本进行替换操作:

    cat file.txt | sed 's/old/new/g'
    

1.1 ls

语法: ls [选项] [目录或文件]

功能:对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息。

常用选项

  • -l 列出文件的详细信息。
  • -a 列出目录下的所有文件,包括以 . 开头的隐含文件。

1.2 pwd

语法: pwd

功能:显示用户当前所在的目录

1.3 cd

Linux系统中,磁盘上的文件和目录被组成一棵目录树,每个节点都是目录或文件。

语法:cd 目录名

功能:改变工作目录。将当前工作目录改变到指定的目录下。

cd .. : 返回上级目录
cd /home/litao/linux/ : 绝对路径
cd ../day02/ : 相对路径
cd ~:进入用户家目
cd -:返回最近访问目录

1.4 touch

语法: touch [选项]… 文件…

功能:touch命令参数可更改文档或目录的日期时间,包括存取时间和更改时间,或者新建一个不存在的文件

1.5 mkdir

语法:mkdir [选项] dirname…

功能:在当前目录下创建一个名为 “dirname” 的目录

常用选项:-p, --parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录

mkdir –p test/test1 : 递归建立多个目录

1.6 rmdir 和 rm

rmdir是一个与mkdir相对应的命令。mkdir是建立目录,而rmdir是删除命令。

语法:rmdir [选项] [dirName]

适用对象:具有当前目录操作权限的所有使用者

功能:删除空目录

常用选项:

  • -f 即使文件属性为只读(即写保护),亦直接删除;(表示强制删除,即不询问用户确认)
  • -i 删除前逐一询问确认;
  • -r 删除目录及其下所有文件;
rm -rf mydir 递归删除该目录以及该目录下所有的文件
rm -rf ./* 命令会删除当前目录下的所有文件和子目录,非隐藏文件
rm -rf * 删除当前目录下的非隐藏文件

1.7 man

访问Linux手册页的命令

语法: man [选项] 命令

常用选项

常用选项包括:

  • -k:根据关键字搜索联机帮助。
  • -a:显示所有章节的手册页内容。默认情况下,man命令从第一章开始搜索,一旦找到匹配的手册页,就停止搜索。但使用-a选项时,按下q退出后,会继续往后面的章节搜索,直到所有章节都搜索完毕。

手册页一般分为以下章节:

  1. 普通命令
  2. 系统调用(例如:openwrite等)
  3. 库函数(例如:printffread等)
  4. 特殊文件,也就是/dev下的各种设备文件
  5. 文件格式(例如:passwd
  6. 游戏相关
  7. 附加信息及其他(例如:一些变量,如environ
  8. 系统管理命令,通常只能由root用户使用(例如:ifconfig

使用man命令可以通过指定章节号来查看特定章节的手册页,例如man 5 passwd将会查看文件格式章节中passwd的手册页。

1.8 cp

**语法:**cp [选项] 源文件或目录 目标文件或目录

功能: 复制文件或目录

说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中。若同时指定多个文件或目录,而最后的目的地并非一个已存在的目录,则会出现错误信息 、常用选项:

  • -f 或 --force 强行复制文件或目录, 不论目的文件或目录是否已经存在
  • -i 或 --interactive 覆盖文件之前先询问用户
  • -r 递归处理,将指定目录下的文件与子目录一并处理。若源文件或目录的形态,不属于目录或符号链接,则一律视为普通文件处理
  • -R 或 --recursive递归处理,将指定目录下的文件及子目录一并处理

1.9 mv

mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录。

语法: mv [选项] 源文件或目录 目标文件或目录

功能:

  1. 视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的目录中。
  2. 当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它 将所给的源文件或目录重命名为给定的目标文件名。
  3. 当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至目标目录中。

常用选项:

  • -f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖
  • -i :若目标文件 (destination) 已经存在时,就会询问是否覆盖!
mv test.txt ../dir1 将dir2中的 test.txt 文件剪切到dir1目录下;
mv dir2.1 ../dir1  将dir2中的 dir2.1目录剪切到dir1目录下;
mv test.txt name.txt 将 test.txt 重命名为 name.txt;

1.10 cat

**语法:**cat [选项] [文件]

功能: 查看目标文件的内容

常用选项:

  • -b 对非空输出行编号
  • -n 对输出的所有行编号
  • -s 不输出多行空行

1.11 more

语法:more [选项] [文件]

功能:more命令,功能类似 cat

常用选项:

  • -n 对输出的所有行编号
  • q 退出more

1.12 less

  • less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大。
  • less 的用法比起 more 更加的有弹性。在 more 的时候,我们并没有办法向前面翻,只能往后面看
  • 但若使用了 less 时,就可以使用 [pageup] [pagedown] 等按键的功能来往前往后翻看文件,更容易用 来查看一个文件的内容!
  • 除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜。

语法: less [参数] 文件

功能: less与more类似,但使用less可以随意浏览文件,而more仅能向前移动,却不能向后移动,而且less在查看之前不会加载整个文件。

选项:

  • -i 忽略搜索时的大小写
  • -N 显示每行的行号
  • /字符串:向下搜索“字符串”的功能
  • ?字符串:向上搜索“字符串”的功能
  • n:重复前一个搜索(与 / 或 ? 有关)
  • N:反向重复前一个搜索(与 / 或 ? 有关)
  • q:quit
history | less

1.13 head

head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾。

语法: head [参数]… [文件]…

功能: head 用来显示档案的开头至标准输出中,默认head命令打印其相应文件的开头10行。

选项:

  • -n<行数> 显示的行数
head -n 20 test.txt 显示test.txt文件中前20行的内容;
tail -n 20 test.txt 显示test.txt文件中后20行的内容;

1.14 tail

tail 命令从指定点开始将文件写到标准输出。使用tail命令的-f选项可以方便的查阅正在改变的日志文件。tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的文件内容。

语法: tail[必要参数] [选择参数] [文件] 功能: 用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理。常用查看日志文件。

选项:

  • -f 循环读取
  • -n<行数> 显示行数
有一个文件共有100行内容,请取出第50行内容<br>
seq 1 100 > test # 生成1到100的序列装入test
方法1 head -n50 test > tmp #将前50行装入临时文件tmp
tail -n1 tmp  # 得到中间行
方法2 head -n50 test | tail -n1
| 的作用是将 head -n50 test 命令的输出作为 tail -n1 命令的输入

1.15 date

date显示

date 指定格式显示时间: date +%Y:%m:%d

date 用法:date [OPTION]… [+FORMAT]

  1. 在显示方面,使用者可以设定欲显示的格式,格式设定为一个加号后接数个标记,其中常用的标记列表如下
    • %H : 小时(00…23)
    • %M : 分钟(00…59)
    • %S : 秒(00…61)
    • %X : 相当于 %H:%M:%S
    • %d : 日 (01…31)
    • %m : 月份 (01…12)
    • %Y : 完整年份 (0000…9999)
    • %F : 相当于 %Y-%m-%d
  2. 在设定时间方面
    • date -s //设置当前时间,只有root权限才能设置,其他只能查看。
    • date -s 20080523 //设置成20080523,这样会把具体时间设置成空00:00:00
    • date -s 01:01:01 //设置具体时间,不会对日期做更改
    • date -s “01:01:01 2008-05-23″ //这样可以设置全部时间 date -s “01:01:01 20080523″ //这样可以设置全部时间
    • date -s “2008-05-23 01:01:01″ //这样可以设置全部时间
    • date -s “20080523 01:01:01″ //这样可以设置全部时间
  3. 时间戳
    • 时间->时间戳:date +%s
    • 时间戳->时间:date -d@1508749502
    • Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp)是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
显示特定格式的日期和时间
date "+%Y-%m-%d %H:%M:%S"

1.16 cal

命令格式: cal [参数] [月份] [年份]

功能: 用于查看日历等时间信息,如只有一个参数,则表示年份(1-9999),如有两个参数,则表示月份和年份

常用选项:

  • -3 显示系统前一个月,当前月,下一个月的月历
  • -j 显示在当年中的第几天(一年日期按天算,从1月1号算起,默认显示当前月在一年中的天数)
  • -y 显示当前年份的日历
cal -y 1997

1.17 echo

echo 是一个常用的命令行工具,用于在终端上输出文本或变量的值。它可以将指定的字符串或变量的值打印到标准输出(通常是终端)上。

echo "Hello, World!" > output.txt

1.18 tree

tree 是一个常用的命令行工具,用于以树状结构显示目录和文件的层次关系。它会递归地遍历指定目录下的所有子目录和文件,并以树状结构的形式在终端上显示它们的层次关系。

安装:

yum install -y tree
tree 显示当前目录的树状结构
tree /path/to/directory 显示指定目录的树状结构

1.19 find

  • Linux下find命令在目录结构中搜索文件,并执行指定的操作。
  • Linux下find命令提供了相当多的查找条件,功能很强大。
  • 即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。
  • 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。

语法: find pathname -options

功能: 用于在文件树种查找文件,并作出相应的处理(可能访问磁盘)

常用选项:

  • -name 按照文件名查找文件
find /usr/bin -name ls

1.20 which/whereis

which 是一个用于定位指定命令在系统路径中的位置的命令行工具。它会搜索系统的 PATH 环境变量中列出的目录,并返回找到的第一个匹配项。

**用法:**which command

whereis 是一个用于查找指定命令、源码文件以及帮助页面(man 手册页)等在系统中的位置的命令行工具。与 which 不同,whereis 可以用于查找更多类型的文件。

**用法:**whereis command

1.21 grep

用于在文本文件中搜索指定模式的行,并将匹配到的行打印出来。它支持使用正则表达式进行高级模式匹配。

语法: grep [选项] 搜寻字符串 文件

功能: 在文件中搜索字符串,将找到的行打印出来

常用选项:

  • -i :忽略大小写的不同,所以大小写视为相同
  • -n :顺便输出行号
  • -v :反向选择,亦即显示出没有 ‘搜寻字符串’ 内容的那一行
  • -l, --files-with-matches: 只显示包含匹配内容的文件名。
  • -c, --count: 只显示匹配行的计数,而不是具体的匹配内容。
  • -r, --recursive: 递归搜索目录及其子目录中的文件。
grep hello file.txt file2.txt  # 在两个文件中搜索
cat file.txt | grep -n '8888'
ps ajx | grep sshd  # 首先使用 ps 命令列出系统中所有的进程信息,然后将输出传递给 grep 命令,在其中搜索包含字符串 "sshd" 的行。换句话说,这个命令用于查找系统中所有与 SSH 服务器进程相关的信息。

1.22 zip/unzip

语法: zip 压缩文件.zip 目录或文件

功能: 将目录或文件压缩成zip格式

常用选项:

  • -r 递归处理,将指定目录下的所有文件和子目录一并处理
将test2目录压缩:zip -r test2.zip test2/*
解压到tmp目录:unzip test2.zip -d /tmp #使用 -d 选项可以将 ZIP 文件解压缩到指定的目录中。

1.23 tar

在Linux中,tar是一个常用的命令行工具,用于创建归档文件(也称为tarball),以及对归档文件进行提取和管理。tar的名称来自于"tape archive"(磁带归档),最初设计是用于备份文件到磁带设备上。

  • -c :建立一个压缩文件的参数指令(create 的意思);
  • -x :解开一个压缩文件的参数指令!
  • -t :查看 tarfile 里面的文件!
  • -z :是否同时具有 gzip 的属性?亦即是否需要用 gzip 压缩?
  • -j :是否同时具有 bzip2 的属性?亦即是否需要用 bzip2 压缩?
  • -v :压缩的过程中显示文件!这个常用,但不建议用在背景执行过程!
  • -f :使用档名,请留意,在 f 之后要立即接档名喔!不要再加参数!
  • -C : 解压到指定目录
tar -czf test.tgz ./*
tar -xzf test.tgz
tar xzf test.tgz -C ../test

1.24 bc指令

bc命令可以很方便的进行浮点运算

echo "1+2+3+4+5+6" | bc

1.25 uname

  • -s--kernel-name:显示内核名称。
  • -n--nodename:显示网络节点主机名。
  • -r--kernel-release:显示内核发布号。
  • -v--kernel-version:显示内核版本。
  • -m--machine:显示处理器类型。
  • -p--processor:显示处理器相关信息。
  • -i--hardware-platform:显示硬件平台。
  • -o--operating-system:显示操作系统名称。
  • -a或–all 详细输出所有信息,依次为内核名称,主机名,内核版本号,内核版本,硬件名,处理器类型,硬件平台类型,操作系统名称
cat /etc/redhat-release

1.26 快捷键

  1. Ctrl + C
    • 功能:中断当前正在运行的程序或命令。
    • 用法:当您在终端中运行一个程序或命令时,按下 Ctrl + C 将立即终止该程序的执行。这对于取消执行长时间运行的命令或停止执行有问题的程序非常有用。
  2. Ctrl + D
    • 功能:表示输入的结束或退出当前 Shell 会话。
    • 用法:
      • 在终端中,按下 Ctrl + D 将发送一个 End-of-File (EOF) 字符给正在等待输入的程序。如果没有在 Shell 中运行任何程序,这将退出当前 Shell 会话。
      • 在交互式 Shell 中,可以使用 Ctrl + D 来结束标准输入流,然后返回到 Shell 提示符。
  3. Ctrl + R
    • 功能:在历史命令中进行反向搜索。
    • 用法:按下 Ctrl + R 将触发反向搜索模式,然后您可以开始输入您想要查找的命令的一部分。Shell 将显示与您输入的部分匹配的最近的历史命令。您可以继续按下 Ctrl + R 来继续搜索,并使用箭头键选择匹配的命令。
  4. Tab 键
    • 功能:自动补全。
    • 用法:在终端中输入命令、文件名或路径时,按下 Tab 键将自动补全当前输入的内容,显示可能的匹配项。如果有多个匹配项,按两次 Tab 键将显示所有匹配项的列表。Tab 键的自动补全功能可以节省大量时间,尤其是当输入较长的文件名或路径时。

1.27 shutdown

语法:shutdown [选项] ** 常见选项:**

  • -h :将系统的服务停掉后,立即关机。
  • -r: 在将系统的服务停掉之后就重新启动
  • -t sec : -t 后面加秒数,亦即『过几秒后关机』的意思

1.28 file

file 是一个在 Unix 和类 Unix 系统中用于确定文件类型的命令。它可以识别文件的格式,例如文本文件、可执行文件、目录、符号链接等,并提供关于文件内容的一些基本信息。

基本用法:

file [options] filename
  • options:一些可选参数,用于指定 file 命令的行为。
  • filename:要检查类型的文件名。

示例:

  1. 检查文件类型
file filename

这会显示 filename 文件的类型信息。

  1. 检查多个文件类型
file file1 file2

这会显示 file1file2 文件的类型信息。

注意事项:

  • file 命令会读取文件的一小部分内容,并尝试根据其特征确定文件类型。但它并不是通过文件扩展名来确定文件类型的。
  • file 命令通常用于确定未知文件的类型,以便在处理文件时了解其内容特征。
  • file 命令还可以识别压缩文件和归档文件,并显示其压缩算法和归档类型。

file 命令对于系统管理员、开发人员和普通用户来说都是一个非常有用的工具,可以帮助他们了解文件的类型和属性,以便在操作文件时做出正确的决策。

1.29 其他命令:

  • 安装和登录命令:login、shutdown、halt、reboot、install、mount、umount、chsh、exit、last;
  • 文件处理命令:file、mkdir、grep、dd、find、mv、ls、diff、cat、ln;
  • 系统管理相关命令:df、top、free、quota、at、lp、adduser、groupadd、kill、crontab;
  • 网络操作命令:ifconfig、ip、ping、netstat、telnet、ftp、route、rlogin、rcp、finger、mail、 nslookup;
  • 系统安全相关命令:passwd、su、umask、chgrp、chmod、chown、chattr、sudo ps、who;
  • 其它命令:tar、unzip、gunzip、unarj、mtools、man、unendcode、uudecode。

shell命令以及运行原理

Linux严格意义上说的是一个操作系统,我们称之为“核心(kernel)“ ,但我们一般用户,不能直接使用kernel。而是通过kernel的“外壳”程序,也就是所谓的shell,来与kernel沟通。如何理解?为什么不能直接使用kernel?

从技术角度,Shell的最简单定义:命令行解释器(command Interpreter)主要包含:

  • 将使用者的命令翻译给核心(kernel)处理。
  • 同时,将核心的处理结果翻译给使用者。

对比windows GUI,我们操作windows 不是直接操作windows内核,而是通过图形接口,点击,从而完成我们的操作(比如进入D盘的操作,我们通常是双击D盘盘符.或者运行起来一个应用程序)。

shell 对于Linux,有相同的作用,主要是对我们的指令进行解析,解析指令给Linux内核。反馈结果在通过内核运 行出结果,通过shell解析给用户。

2. Linux权限管理

Linux权限的概念

Linux下有两种用户:超级用户(root)、普通用户。

  • 超级用户:可以再linux系统下做任何事情,不受限制 普通用户:在linux下做有限的事情。
  • 超级用户的命令提示符是“#”,普通用户的命令提示符是“$”。

2.0 su/sudo和添加修改用户管理员

2.0.1 su

su 是一个 Unix 和类 Unix 系统中的命令,用于切换用户身份。su 的全称是 “substitute user”,意思是用另一个用户的身份来执行命令。

基本用法:

su [options] [username]
  • options:一些可选参数,用于指定 su 命令的行为。
  • username:要切换到的目标用户的用户名。如果未指定用户名,则默认切换到超级用户(root)。

示例:

  1. 切换到超级用户(root)

    su
    

    这将要求您输入超级用户的密码,然后将当前 shell 切换到 root 用户的 shell。

  2. 切换到指定用户

    su username
    

    这将要求您输入指定用户的密码,然后将当前 shell 切换到指定用户的 shell。

常用选项:

  • -c command:执行完命令后返回原来的用户。
  • -l--login:模拟登录。以新用户的环境启动一个新的 shell。

注意事项:

  • 如果没有提供用户名,则默认切换到超级用户(root)。
  • 切换到其他用户需要输入目标用户的密码,除非当前用户是超级用户(root),那么不需要密码。
  • 在切换用户后,可以执行相应的命令,执行完毕后可以使用 exit 命令返回原来的用户。
  • 超级用户可以切换到任何用户,但其他用户只能切换到自己或者是共享同一组的用户。

su 命令在系统管理中非常有用,可以让管理员在需要的时候切换到其他用户来执行相应的任务,例如需要进行系统维护或配置文件权限时。

2.0.2 sudo

sudo 是 Unix 和类 Unix 系统中的命令,用于以其他用户的身份执行命令,通常是以超级用户(root)的身份执行。sudo 的全称是 “superuser do”。

基本用法:

sudo [options] command [arguments]
  • options:一些可选参数,用于指定 sudo 命令的行为。
  • command:要执行的命令。
  • arguments:命令的参数。

示例:

  1. 以 root 身份执行命令

    sudo command
    

    这将要求您输入当前用户的密码(不是 root 的密码),然后以 root 身份执行指定的命令。

  2. 以其他用户身份执行命令

    sudo -u username command
    

    这将要求您输入当前用户的密码(不是目标用户的密码),然后以指定用户的身份执行指定的命令。

常用选项:

  • -u username:指定要以哪个用户的身份执行命令。
  • -l--list:列出当前用户可以使用 sudo 执行的命令。
  • -i:使用目标用户的环境变量。
  • -s:使用目标用户的 shell。

注意事项:

  • 默认情况下,sudo 命令只能由系统管理员配置的用户执行,且执行时需要输入当前用户的密码,而不是目标用户的密码。
  • sudo 的权限配置通常存储在 /etc/sudoers 文件中,只有具有适当权限的用户才能使用 sudo 命令执行特定的命令。
  • 使用 sudo 命令可以避免用户登录到超级用户(root)账户,从而提高系统安全性。
  • 在安装和配置软件、修改系统配置等需要管理员权限的操作中经常使用 sudo 命令。

whoami 用于显示当前登录用户的用户名。

2.0.3 添加修改管理员

  1. 添加用户:

使用 adduser 命令添加用户:

sudo adduser username

这会创建一个新用户,并在 /home 目录下创建以用户名命名的目录作为用户的主目录。

设置用户密码:

sudo passwd username

这会提示您输入用户的密码。密码会被加密并存储在系统中。

  1. 查看用户:

查看所有用户:

cat /etc/passwd

这会列出系统上的所有用户。每个用户的信息都在一行中,以冒号分隔。

查看用户组:

cat /etc/group

这会列出系统上的所有用户组,每个用户组的信息也在一行中,以冒号分隔。

  1. 删除用户:

使用 deluser 命令删除用户:

sudo deluser username

这会从系统中删除指定的用户,但不会删除用户的主目录。

删除用户主目录:

sudo rm -r /home/username

这会删除用户的主目录及其所有内容。

  1. 添加管理员:

将用户添加到 sudo 组:

sudo usermod -aG sudo username

这会将用户添加到 sudo 组,从而赋予了用户使用 sudo 命令获得管理员权限的能力。

添加管理员后验证:

sudo su - username

这会使用新的管理员用户登录,并测试其是否具备管理员权限。

  1. 删除管理员:

从 sudo 组中删除用户:

sudo deluser username sudo

这会从 sudo 组中移除指定用户,使其失去管理员权限。

  1. 删除用户:
sudo deluser username

这会从系统中删除指定的用户,但不会删除用户的主目录。

  1. 删除用户主目录:
sudo rm -r /home/username

这会删除用户的主目录及其所有内容。

  1. /etc/sudoers

打开 /etc/sudoers 文件,并使用文本编辑器(通常是 vivim)进行编辑。

vim /etc/sudoers

在打开的文件中找到合适的位置添加新的行来授权管理员权限。通常,可以在文件的末尾添加。

在添加新行时,请使用以下格式来授权管理员权限:

username ALL=(ALL:ALL) ALL

2.1 文件访问者的分类(人)

  1. 用户(User)
    • 表示文件或目录的所有者,即创建该文件或目录的用户。在权限列表中,用字母 u 表示。
    • 用户对文件或目录拥有的权限是最具体和重要的,因为它们对应于文件或目录的所有者。
  2. 组(Group)
    • 表示文件或目录的所属组,即与所有者属于同一组的用户。在权限列表中,用字母 g 表示。
    • 组权限指定了与所有者在同一组的其他用户对文件或目录的访问权限。
  3. 其他人(Others)(也称为 “世界”):
    • 表示除文件所有者和所属组成员之外的所有其他用户。在权限列表中,用字母 o 表示。
    • 其他人权限指定了除所有者和组成员之外的所有用户对文件或目录的访问权限。

这三种访问者分类在文件或目录的权限中各自拥有自己的权限位,通常表示为三个字符的形式:r(读取权限)、w(写入权限)和x(执行权限)。因此,文件或目录的权限列表通常由九个字符组成,分为三组,每组三个字符,分别代表用户、组和其他人的权限。

例如,-rwxr-xr-- 表示一个文件的权限,其中:

  • 前三个字符 rwx 表示文件所有者拥有读、写和执行权限。
  • 接下来的三个字符 r-x 表示与所有者同组的其他用户拥有读和执行权限,但没有写权限。
  • 最后的三个字符 r-- 表示其他所有用户只拥有读权限,没有写入和执行权限。

2.2 文件类型和访问权限(事物属性)

drwxr-xr-x 3 root root 4096 Mar 20 15:30 file1

  1. drwxr-xr-x:这个部分表示文件的权限。其中:
    • 第一个字符 d 表示这是一个目录。如果是普通文件,则显示 -
    • 接下来的三个字符 rwx 表示文件所有者(root)拥有读、写和执行权限。
    • 接下来的三个字符 r-x 表示与所有者同组的用户(也是root所在的组)拥有读和执行权限,但没有写权限。
    • 最后的三个字符 r-x 表示其他用户(不是root用户,也不在root所在的组)也具有读和执行权限,但没有写权限。
  2. 3:这个数字表示链接的数量。对于目录来说,通常会有一个链接指向它自身,然后根据其中包含的子目录和文件数量,会有额外的链接。
  3. root root:这两个部分分别表示文件的所有者和所属组。在这个例子中,文件所有者和所属组都是 “root” 用户/组。
  4. 4096:这是文件的大小,以字节为单位。对于目录来说,它显示的是目录所占用的空间大小,通常为 4096 字节,因为目录本身也是一个文件,需要占用一定的空间。
  5. Mar 20 15:30:这是文件的最后修改时间。
  6. file1:这是文件的名称。

a) 文件类型

d:文件夹-:普通文件
l:软链接(类似Windows的快捷方式)
b:块设备文件(例如硬盘、光驱等)
p:管道文件
c:字符设备文件(例如屏幕等串口设备)
s:套接口文件

b) 基本权限

i. 读权限(r/4)

  • 对于文件:具有读取文件内容的权限,允许用户查看文件的内容。
  • 对于目录:具有浏览该目录信息的权限,允许用户列出目录下的文件和子目录。

ii. 写权限(w/2)

  • 对于文件:具有修改文件内容的权限,允许用户编辑、修改或删除文件内容。
  • 对于目录:具有删除和移动目录内文件的权限,允许用户向目录中添加、删除或移动文件和子目录。

iii. 执行权限(x/1)

  • 对于文件:具有执行文件的权限,允许用户运行可执行文件或脚本。
  • 对于目录:具有进入目录的权限,允许用户使用该目录作为当前工作目录,以便访问其中的文件和子目录。

在Linux/Unix系统中,这些权限位会以数字形式表示,其中读权限对应的数字值为4,写权限对应的数字值为2,执行权限对应的数字值为1。通常,将这些权限位的数字值相加,以得到对应的权限组合。

例如,如果一个文件的权限是 rw-r--r--,其中:

  • 用户拥有读写权限(4 + 2 = 6),
  • 同组用户拥有读权限(4),
  • 其他用户也拥有读权限(4)。

所以,这个文件的权限以数字形式表示为 644。

2.3 文件权限值的表示方法

8进制数值表示方法

权限符号八进制二进制
r4100
w2010
x1001
rw6110
rx5101
wx3011
rwx7111
0000

2.4 文件访问权限的相关设置方法

2.4.1 chmod

chmod 是一个在 Unix 和类 Unix 系统中常用的命令,用于更改文件或目录的权限。它的名字是 “change mode” 的缩写。

基本用法:

chmod [options] mode file(s)
  • options:一些可选参数,用于指定 chmod 命令的行为。
  • mode:要设置的权限模式。可以使用数字模式(如 755)或符号模式(如 u+rwx)。
  • file(s):要更改权限的文件或目录。

数字模式:

chmod 755 file.txt

这将为文件 file.txt 设置权限 rwxr-xr-x,即所有者具有读、写、执行权限,而组成员和其他用户只有读和执行权限。

符号模式:

在符号模式中,可以使用符号来设置权限。符号模式由以下组件组成:

  • u:文件所有者
  • g:与所有者同组的用户
  • o:其他用户
  • a:所有用户(ugo 的组合)
  • +:添加权限
  • -:移除权限
  • =:设置权限

示例:

chmod u=rwx,g=rx,o=r file.txt
chmod u+w /home/abc.txt
chmod o-x /home/abc.txt

常用选项:

  • -R:递归地更改目录及其所有内容的权限。
  • -v:显示 chmod 命令的操作过程。
  • -f:在权限更改期间抑制错误消息。

2.4.2 chown

chown 是一个 Unix 和类 Unix 系统中的命令,用于更改文件或目录的所有者和/或所属组。

基本用法:

bashCopy code
chown [options] new_owner[:new_group] file(s)
  • options:一些可选参数,用于指定 chown 命令的行为。
  • new_owner[:new_group]:要设置的新所有者和/或新所属组。可以指定新的所有者、新的所属组,或同时指定新的所有者和新的所属组,用冒号 : 分隔。
  • file(s):要更改所有者和/或所属组的文件或目录。

示例:

  1. 仅更改所有者

    chown new_owner file.txt
    

    这将把文件 file.txt 的所有者更改为 new_owner

  2. 仅更改所属组

    chown :new_group file.txt
    

    这将把文件 file.txt 的所属组更改为 new_group

  3. 同时更改所有者和所属组

    chown new_owner:new_group file.txt
    

    这将把文件 file.txt 的所有者更改为 new_owner,并将所属组更改为 new_group

  4. 递归更改目录及其内容的所有者和/或所属组

    chown -R new_owner:new_group directory/
    

    这将递归地更改目录 directory/ 及其所有内容(包括子目录和文件)的所有者和/或所属组。

常用选项:

  • -R:递归地更改目录及其内容的所有者和/或所属组。
  • -v:显示 chown 命令的操作过程。
  • -f:在更改期间抑制错误消息。

2.4.3 chgrp

chgrp 是一个 Unix 和类 Unix 系统中的命令,用于更改文件或目录的所属组。

基本用法:

bashCopy code
chgrp [options] new_group file(s)
  • options:一些可选参数,用于指定 chgrp 命令的行为。
  • new_group:要设置的新所属组。
  • file(s):要更改所属组的文件或目录。

示例:

  1. 仅更改所属组

    chgrp new_group file.txt
    

    这将把文件 file.txt 的所属组更改为 new_group

  2. 递归更改目录及其内容的所属组

    chgrp -R new_group directory/
    

    这将递归地更改目录 directory/ 及其所有内容(包括子目录和文件)的所属组。

常用选项:

  • -R:递归地更改目录及其内容的所属组。
  • -v:显示 chgrp 命令的操作过程。
  • -f:在更改期间抑制错误消息。

2.4.4 umask

umask 是一个在 Unix 和类 Unix 系统中用于设置文件权限掩码的命令。文件权限掩码决定了在创建新文件或目录时所应用的默认权限。

基本用法:

umask [mode]
  • mode:可以是一个八进制数字,用来表示文件权限掩码。如果省略,则 umask 命令会显示当前的文件权限掩码。

文件权限掩码的作用:

文件权限掩码会影响新创建的文件或目录的权限设置。它通过从完整权限集中去除一部分权限来限制新文件或目录的权限。

例如,一个权限掩码为 0022 的设置表示:

  • 对于新创建的文件,去除了其他用户的写权限,因此新文件的权限为 644
  • 对于新创建的目录,去除了其他用户的写和执行权限,因此新目录的权限为 755

示例:

  1. 查看当前文件权限掩码:
umask

这将显示当前的文件权限掩码,通常以八进制数字形式显示。

  1. 设置新的文件权限掩码:
umask 022

这将设置文件权限掩码为 022,限制其他用户的写权限。

超级用户默认掩码值为0022,普通用户默认为0002。

权限计算方法:mask & ~umask

注意事项:

  • 文件权限掩码的设置是透明的,即它不会影响已有文件或目录的权限。
  • 文件权限掩码通常在用户的配置文件(如 .bashrc.profile)中设置,以便在用户登录时自动应用。
  • 文件权限掩码的设置是进程级别的,一旦设置,它会影响该进程及其子进程创建的文件或目录。

umask 命令允许用户自定义文件和目录的默认权限,从而提高系统安全性和灵活性。

2.4.5 粘滞位

粘滞位(Sticky Bit)是 Unix 和类 Unix 操作系统中用于文件和目录的特殊权限标记之一。粘滞位通常用于目录上,它可以防止普通用户删除其他用户创建的文件,除非用户拥有适当的权限。在文件上,粘滞位的作用不太常见。

在目录上的作用:

当粘滞位被设置在目录上时,只有目录的所有者、文件的所有者或超级用户才能删除在该目录下创建的文件。即使其他用户对这些文件有写权限,他们也无法删除这些文件。这有助于防止用户误删其他用户的文件。

如何设置粘滞位:

在 Unix 系统中,可以使用 chmod 命令来设置粘滞位。要将粘滞位设置在目录上,可以使用数字表示法设置权限,其中将 1 添加到权限位中表示设置粘滞位。例如,要将粘滞位设置在目录 /path/to/directory 上,可以运行以下命令:

chmod +t /path/to/directory

或者,您可以使用符号表示法:

chmod o+t /path/to/directory

如何检查是否设置了粘滞位:

要检查目录是否设置了粘滞位,可以使用 ls 命令的长格式(-l)来查看目录的权限。如果目录的权限位中包含 t(例如 drwxr-xr-t),则表示已设置粘滞位。

示例:

假设目录 /tmp 设置了粘滞位,这意味着只有目录的所有者、文件的所有者或超级用户才能删除在 /tmp 目录下创建的文件。

注意事项:

  • 粘滞位通常在公共目录(如 /tmp)上设置,以防止普通用户删除其他用户的临时文件。
  • 在某些 Unix 系统上,当粘滞位设置在目录上时,还可能会导致文件仅能由文件的所有者或超级用户进行重命名或移动,而不是任意用户。

粘滞位是 Unix 系统中的一种重要的权限机制,它有助于保护文件和目录不受意外删除的影响,并提高了系统的安全性。

3. Linux开发工具

3.1 Linux 软件包管理器 yum

Yum(Yellowdog Updater Modified)是一种在Linux操作系统上用于管理软件包的工具。它最初是为了简化Red Hat Linux系统上的软件包管理而开发的,后来也被许多其他Linux发行版采用。下面是yum的一些关键特点和用法:

  1. 软件包安装和升级:Yum可以方便地安装、更新和卸载软件包。用户可以使用简单的命令来管理系统上的软件包,而无需手动下载和安装。
  2. 依赖性解决:Yum可以自动解决软件包之间的依赖关系。当安装或升级软件包时,它会自动下载和安装所需的其他软件包,确保系统的完整性和稳定性。
  3. 软件源管理:Yum通过软件源(repositories)来获取软件包。用户可以轻松地添加、删除或禁用软件源,以便获取所需的软件包或版本。
  4. 插件系统:Yum支持插件系统,允许用户根据自己的需求扩展其功能。例如,有一些插件可以加速下载速度、改善依赖关系解决方案等。
  5. 命令行界面:Yum主要通过命令行界面进行操作,但也有一些基于图形界面的前端工具可用于更直观地管理软件包。
  6. 使用示例:以下是一些常用的yum命令示例:
    • yum install package_name: 安装软件包。
    • yum update package_name: 更新软件包。
    • yum remove package_name: 卸载软件包。
    • yum search keyword: 搜索软件包。
    • yum list: 列出系统中所有可用的软件包。
    • yum clean [all/packages]: 清理缓存或旧的软件包

示例:

[Eriri@VM-16-14-centos ~]$ yum list | grep lrzsz
lrzsz.x86_64                             0.12.20-36.el7                @os    
  1. lrzsz.x86_64:这部分指示了软件包的名称以及它适用的平台和架构。其中,“lrzsz”是软件包的名称,“x86_64”表示该软件包适用于64位的x86架构处理器。
  2. 0.12.20-36.el7:这一部分提供了软件包的版本信息。具体来说,“0.12.20”是软件包的主版本号和次版本号,“36”是源程序发行号和软件包的发行号,“el7”表示操作系统发行版的版本,这里代表CentOS 7或者Red Hat Enterprise Linux 7。
  3. @base:最后一部分指示了软件包所属的“软件源”。在这个例子中,“base”代表基础软件源,类似于一个应用商店的概念,是指软件包所在的主要源,可能包含了操作系统的核心软件包和一些常用的工具软件。

综上所述,这个文本描述告诉我们这个软件包是lrzsz的64位版本,版本号为0.12.20-36,适用于CentOS 7或Red Hat Enterprise Linux 7,并且来自于基础软件源。

注意事项:

  1. 网络连接需畅通:在使用yum进行软件包管理之前,确保主机(或虚拟机)的网络连接正常。可以通过使用ping命令来验证网络连接是否畅通,比如ping www.baidu.com
  2. 查看软件包:使用yum list命令可以列出当前系统中所有的软件包。若要筛选出特定软件包,可以结合使用grep命令,比如yum list | grep lrzsz,这将只显示包含"lrzsz"的软件包。
  3. 软件包命名规则:软件包的命名规则通常包括主版本号、次版本号、源程序发行号、软件包发行号、主机平台和CPU架构等信息。在选择软件包时,需要确保与系统的配置匹配,比如系统是64位还是32位。
  4. 安装软件:通过yum install命令可以很方便地安装软件包,例如sudo yum install lrzsz将安装lrzsz软件包。在安装过程中,可能需要输入y来确认安装。安装完成后,会显示"complete"字样。
  5. 注意事项
    • 安装软件时需要有足够的权限,通常需要使用sudo或切换到root账户。
    • 一次只能安装一个软件包,如果同时尝试安装多个软件包,yum会报错。
    • 如果遇到yum报错,可以通过搜索引擎自行解决或查找解决方法。

3.2 vim

vim

3.2.1 vim的基本概念

Vim(Vi Improved)是一个经典的文本编辑器,它是Unix和类Unix操作系统中最常用的编辑器之一。Vim最初是由Bram Moolenaar于1991年发布的,是Unix系统上的vi编辑器的增强版本。它具有许多强大的功能,包括高度可定制性、广泛的插件支持以及强大的编辑和浏览功能。

Vim有几种主要的工作模式,它们分别是:

  1. 普通模式(Normal Mode)
    • 在普通模式下,你可以控制屏幕光标的移动,删除字符、字或行,复制、粘贴或移动文本段落,以及执行各种编辑命令。
    • 进入普通模式后,你可以按下键盘上的各种键来执行不同的命令,比如h、j、k、l来左、下、上、右移动光标,x来删除字符,dd来删除整行等。
    • 通过普通模式,你可以进入插入模式或末行模式。
  2. 插入模式(Insert Mode)
    • 在插入模式下,你可以进行文本输入。只有在插入模式下,你才能输入文本。
    • 普通模式下按下i、a、o等键可以进入插入模式,分别表示在当前光标前插入、当前光标后插入、在当前行下方新开一行并插入。
  3. 末行模式(Last Line Mode)
    • 也称为命令模式或Ex模式。在末行模式下,你可以执行文件保存、退出、搜索、替换等操作。
    • 进入末行模式的方式是在普通模式下按下冒号(:)键。例如,输入:w保存文件,:q退出Vim等。
    • 在末行模式下,还可以输入:help来查看Vim的帮助文档。

3.2.2 vim的基本操作

  1. 进入Vim:在终端中输入vim命令,后跟文件名,例如vim test.c,这将打开名为test.c的文件,如果文件不存在则会新建一个空文件。
  2. 从正常模式切换至插入模式
    • 在正常模式下按下i键,将光标置于当前位置并进入插入模式,可以开始输入文本。
    • 按下a键,将光标置于当前字符之后并进入插入模式,可以在光标后开始输入文本。
    • 按下o键,将在当前行的下一行插入一个新的空行,并进入插入模式,可以在新行开始输入文本。
  3. 从插入模式切换至正常模式
    • 按下ESC键,退出插入模式,回到正常模式。在正常模式下可以执行各种编辑命令。
  4. 从正常模式切换至末行模式
    • 在正常模式下按下:键,进入末行模式。在末行模式下可以执行一些文件操作命令。
  5. 保存文件及退出Vim
    • 在末行模式下输入:w,然后按下回车键,可以保存当前文件。
    • 输入:wq,然后按下回车键,可以保存当前文件并退出Vim。
    • 输入:q!,然后按下回车键,可以强制退出Vim而不保存文件更改。

3.2.3 vim正常模式的命令集

以下是Vim正常模式下的一些常用命令:

  1. 插入模式
    • i:在光标当前位置进入插入模式,开始输入文字。
    • a:在光标所在位置的下一个位置进入插入模式,开始输入文字。
    • o:在光标所在行的下一行插入一个新行,并进入插入模式,开始输入文字。
  2. 从插入模式切换为正常模式
    • ESC键:退出插入模式,返回正常模式。
  3. 移动光标
    • h:左移一个字符。
    • j:下移一行。
    • k:上移一行。
    • l:右移一个字符。
    • G:移动到文件的最后一行。
    • $:移动到光标所在行的行尾。
    • ^:移动到光标所在行的行首。
    • w:跳到下一个单词的开头。
    • e:跳到下一个单词的结尾。
    • b:回到上一个单词的开头。
    • #l:移动到当前行的第#个字符处。
    • gg:跳到文件的第一行。
    • shift + g:跳到文件的最后一行。
    • ctrl + b:向后移动一页。
    • ctrl + f:向前移动一页。
    • ctrl + u:向后移动半页。
    • ctrl + d:向前移动半页。
  4. 删除文字
    • x:删除光标所在位置的字符。
    • #x:删除光标后#个字符。
    • X:删除光标前的一个字符。
    • #X:删除光标前#个字符。
  5. 剪贴
    • dd:剪贴光标所在行。
    • #dd:从光标所在行开始剪贴#行。
  6. 复制与粘贴
    • yw:复制光标所在位置到单词结尾的字符。
    • #yw:复制#个单词。
    • yy:复制光标所在行。
    • #yy:复制光标所在行开始的#行。
    • p:粘贴复制的内容到光标后。
  7. 替换
    • r:替换光标所在位置的字符。
    • R:进入替换模式,替换光标所在位置之后的字符,直到按下ESC键。
  8. 撤销与恢复
    • u:撤销上一次操作。
    • ctrl + r:恢复撤销的操作。
  9. 更改
    • cw:更改光标所在处到单词结尾的字符。
    • c#w:更改#个单词。
  10. 跳至指定的行
    • ctrl + g:显示光标所在行的行号。
    • #G:跳到文件的第#行。

3.2.4 vim末行模式命令集

  1. 列出行号
    • set nu:在每一行前面显示行号。
  2. 跳到文件中的某一行
    • #:#表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行,例如输入数字15,再回车,就会跳到文件的第15行。
  3. 查找字符
    • /关键字:先按/键,再输入想要寻找的字符。按下回车后,会从光标位置开始向文件末尾搜索该关键字,按下n继续查找下一个匹配项。
    • ?关键字:先按?键,再输入想要寻找的字符。按下回车后,会从光标位置开始向文件开头搜索该关键字,按下n继续查找上一个匹配项。

/? 在查找上的区别:

  • /是向后搜索,从光标位置向文件末尾搜索关键字。
  • ?是向前搜索,从光标位置向文件开头搜索关键字。
  1. 保存文件
  • :w:在冒号后输入字母w,表示保存文件。
  1. 离开Vim
  • :q:按下:q就是退出Vim。如果有未保存的修改,会提示保存或放弃修改。
  • :q!:如果无法正常退出Vim(比如有未保存的修改),可以在:q后跟一个!强制退出Vim,放弃所有修改。
  • :wq:一般建议在退出时搭配:w一起使用,这样在退出时还可以保存文件。
    执行外部 shell 命令
    通过在 Vim 的命令模式下输入 :!,然后跟随一个外部 shell 命令,你可以在 Vim 中直接执行该命令,而不必退出 Vim 然后在终端中执行。

使用 ! 可以让你在 Vim 中执行各种命令,如编译代码、运行程序、查看文件内容等。这样可以提高编辑效率,特别是在需要频繁地切换 Vim 和终端之间时。

!man 3 sleep

多文件操作:

  1. 打开多个文件

    • 在命令行中使用 vim 命令并列出要打开的文件名。例如:vim file1.txt file2.txt
    • 打开 Vim 后,可以使用 :e 命令来在当前窗口中打开其他文件,或者使用 :split:vsplit 命令来在新的水平或垂直分割窗口中打开其他文件。
  2. 在多个文件之间切换

    • 使用 :n:next 命令切换到下一个文件。
    • 使用 :N:Next 命令切换到上一个文件。
    • 使用 :e <file> 命令切换到指定的文件。

    Ctrl + w 是一个非常有用的组合键,它用于在不同的窗口之间进行切换。按下 Ctrl + w 后,紧跟着按下第二个 w 键(即 Ctrl + w + w),会使光标在打开的窗口中循环向前移动。

  3. 查看打开文件列表

    • 在 Vim 中输入 :ls 命令可以查看当前打开的文件列表。这会显示文件编号、文件名以及是否有未保存的修改。
  4. 在多个文件之间进行搜索和替换

    • 使用 :args 命令来列出当前打开的所有文件。
    • 使用 :argdo 命令可以对所有文件执行相同的操作。例如::argdo %s/foo/bar/g | w 将在所有文件中将 foo 替换为 bar 并保存修改。
  5. 在多个文件之间进行复制和粘贴

    • 在可视模式下选择要复制的文本。
    • 使用 "+y 命令将选定文本复制到系统剪贴板中。
    • 切换到另一个文件,在想要粘贴的位置,使用 "+p 命令将内容粘贴进去。
  6. 关闭文件

    • 在 Vim 中输入 :q 命令可以关闭当前窗口,如果只有一个窗口打开,则关闭 Vim。
    • 使用 :qa 命令可以关闭所有窗口并退出 Vim。

3.2.5 可视模式

  1. 注释多行文本
    • 进入 Vim 并打开你要编辑的文件。
    • 移动光标到你想要注释的第一行的开头。
    • 按下 Ctrl + v 进入可视块模式,或者按下 V 进入可视行模式(根据你想要注释的是单个字符还是整行文本而定)。
    • 使用光标键移动到你想要注释的最后一行或最后一个字符。
    • 按下 Shift + I(大写的 I,不是小写的 i)进入插入模式,并在光标左侧添加注释符号(如 #)。
    • 输入你想要添加的注释符号后,按下 ESC 键。此时你会发现所有选择的行或字符的行首都添加了注释符号。
  2. 取消注释多行文本
    • 进入可视模式(可视块模式或可视行模式)并选择你想要取消注释的区域。
    • 按下 d 键将选定的注释删除。

请注意,注释符号(如 #)和取消注释符号(如果是块注释,通常是删除)可能因文件类型或编程语言而异。确保你选择的符号与你的文件类型和语言一致。

  1. 选中整段
  • 按行选中
    • 光标放在你要选中的行上。
    • 按下 Shift + v 进入行可视模式(Visual Line mode)。
  • 使用指令选中
    • 使用 :n(其中 n 是行号)跳转到特定行。
    • 按下 V 进入行可视模式。
  • 选中多行
    • 使用 Shift + v 进入行可视模式。
    • 使用上下箭头键或 jk 键移动光标来选中多行。
  • 全选
    • 使用 ggVG 来全选文件内容。
      删除可以按d

3.2.6 vim小技巧

在 Vim 中,你可以使用 + 符号跟随文件名,后面紧跟一个行号,以便在打开文件时将光标定位到指定的行号。这对于在编辑大型文件时,快速导航到特定行是非常有用的。

例如,你可以执行以下命令以将光标定位到 test.c 文件的第 7 行:

Copy codevim test.c +7

3.2.7 vim配置

VimForCpp: 快速将vim打造成c++ IDE (gitee.com)

3.3 Linux编译器-gcc/g++使用

3.3.1 编译

编译过程是将高级源代码转换为可执行代码或库文件的过程。

  1. 预处理(进行宏替换): 在编译过程开始之前,源代码经过预处理器的处理。预处理器执行的主要任务是进行宏替换和文件包含操作。宏替换是指将代码中定义的宏(例如#define指令)替换为其对应的实际内容。预处理器还会处理#include指令,将其他源代码文件中的内容包含到当前文件中。预处理器在这个阶段不会生成任何中间或目标代码,它只是对源代码进行简单的文本处理。
  2. 编译(生成汇编): 在这一阶段,预处理后的源代码被编译器处理。编译器将源代码转换为汇编语言。汇编语言是一种较为低级的语言,它使用类似于机器指令的符号来表示计算机指令。编译器会将高级语言中的变量、函数等转换成相应的汇编语言代码,但仍然保留了高级语言的逻辑结构。
  3. 汇编(生成机器可识别代码): 汇编器(assembler)将汇编语言代码转换成机器码,也就是计算机可以直接执行的指令。汇编器将每一条汇编语言指令转换为其对应的二进制表示形式,并生成目标文件。目标文件中包含了机器指令以及相关的元数据信息。
  4. 连接(生成可执行文件或库文件): 连接器(linker)将汇编生成的目标文件与其他必要的目标文件(如库文件)进行连接,生成最终的可执行文件或库文件。连接器的主要任务包括解析符号引用、符号重定位以及生成目标文件之间的链接关系。最终生成的可执行文件包含了所有必要的机器码以及相关的元数据信息,可以在计算机上直接运行。

3.3.2 gcc

gcc是一个广泛使用的编译器,可以编译多种语言。

  1. 预处理(进行宏替换): 预处理阶段是编译过程的第一步,它负责对源代码进行宏替换、文件包含、条件编译以及去注释等操作。预处理器会根据源代码中的预处理指令(以#开头的代码行)来进行处理。gcc提供了-E选项,用于指示编译器只执行预处理操作,并将预处理后的结果输出到指定文件中。例如:

    gcc -E hello.c -o hello.i
    

    这个命令将对hello.c进行预处理,并将结果保存到hello.i文件中。

  2. 编译(生成汇编): 编译阶段将预处理后的代码转换成汇编代码。在这个阶段,gcc会检查代码的语法和语义,并将其翻译成相应的汇编语言代码。使用-S选项可以让gcc只进行编译操作,生成汇编文件,而不进行汇编和链接。例如:

    gcc -S hello.i -o hello.s
    

    这个命令将对预处理后的文件hello.i进行编译,并将结果保存到hello.s文件中。

  3. 汇编(生成机器可识别代码): 汇编阶段将汇编代码转换成机器可识别的目标文件。使用-c选项可以让gcc只进行汇编操作,生成目标文件。例如:

    gcc -c hello.s -o hello.o
    

    这个命令将对汇编文件hello.s进行汇编,并将结果保存到hello.o文件中。

  4. 连接(生成可执行文件或库文件): 链接阶段将目标文件链接成可执行文件或库文件。在链接阶段,gcc会解析符号引用、符号重定位,并生成最终的可执行文件或库文件。例如:

    gcc hello.o -o hello
    

    这个命令将目标文件hello.o链接成可执行文件hello

动静态库

  1. 静态库
    • 静态库是一组已经编译好的目标文件的集合,其中包含了一些函数和数据,以及用于链接这些函数和数据的信息。
    • 在编译链接时,静态库的内容会被整体复制到生成的可执行文件中。
    • 静态库的文件通常以 .a 结尾,例如 libexample.a
    • 静态库的优点是,一旦链接完成,程序就不再依赖外部的库文件,所有的代码都包含在生成的可执行文件中,因此在部署时非常方便。
    • 但静态库的缺点是,如果多个程序都使用了相同的静态库,那么每个程序都会包含一份该静态库的副本,可能会导致生成的可执行文件比较大,并且在系统上占用了更多的空间。
  2. 动态库
    • 动态库是一组已经编译好的目标文件的集合,其中包含了一些函数和数据,但并没有被整体复制到生成的可执行文件中。
    • 在编译链接时,程序只是引用了动态库中函数和数据的位置信息,并没有将其具体内容包含在生成的可执行文件中。
    • 动态库的文件通常以 .so 结尾,例如 libexample.so
    • 动态库的优点是,多个程序可以共享同一个动态库的副本,节省了系统资源,并且在程序运行时动态加载库文件,可以减小可执行文件的体积,提高程序的运行效率。
    • 但动态库的缺点是,如果系统中缺少了某个动态库,或者某个动态库的版本发生了变化,可能会导致程序无法正常运行;此外,动态库的加载和卸载会增加程序的启动和关闭时间,可能会降低程序的性能。

在C程序中,像 printf 这样的标准库函数实际上并不是在源代码中进行定义的,而是在标准C库中已经实现了。在Linux系统中,通常这些标准C库函数会被编译成动态链接库(也称为共享对象库,Shared Object Library),在系统中以.so文件的形式存在。

这些库文件包含了标准C库函数的实现代码,比如 printfscanfmalloc 等等。当我们在C程序中调用这些函数时,编译器并不会将它们的具体实现代码直接编译进生成的可执行文件中,而是在链接阶段去找到对应的库文件,将函数调用链接到库文件中的实现代码上。

因此,在编译C程序时,gcc默认会去系统默认的搜索路径(例如/usr/lib)下查找这些标准C库的动态链接库文件(通常是libc.so.6),并将程序链接到这些库文件中的函数实现上。这样,最终生成的可执行文件中并不包含这些标准C库函数的具体实现代码,而是在运行时动态加载对应的库文件。

gcc选项

  1. -c: 编译源文件,但不进行链接。生成目标文件(.o 文件),而不生成可执行文件。这个选项通常在将多个源文件编译成目标文件时使用。
  2. -o : 指定输出文件的名称。使用这个选项可以将编译后的结果保存到指定的文件中,而不是默认的输出文件中。
  3. -E: 只执行预处理操作,生成预处理后的结果并输出到标准输出流中,通常与 -o 选项一起使用来将预处理结果保存到文件中。
  4. -S: 只执行编译操作,生成汇编代码并输出到标准输出流中,通常与 -o 选项一起使用来将汇编代码保存到文件中。
  5. -g: 生成包含调试信息的可执行文件或目标文件,以便于调试程序。这些调试信息可以被调试器(如 GDB)使用。
  6. -Wall: 开启所有警告信息,包括一些编码中可能存在的潜在问题的警告。
  7. -O: 设置优化级别。可以使用 -O0-O1-O2-O3 等选项来指定不同的优化级别,-O0 表示不进行优化,-O3 表示优化级别最高。
  8. -static: 强制使用静态链接,生成静态链接的可执行文件。默认情况下,gcc 使用动态链接。
  9. -shared: 生成共享对象(动态链接库)。通常用于编译生成共享库文件。
  10. -I : 添加包含文件搜索路径。指定编译器搜索头文件的目录,可以多次使用该选项来添加多个搜索路径。
  11. -L : 添加库文件搜索路径。指定编译器搜索库文件的目录,可以多次使用该选项来添加多个搜索路径。
  12. -l: 指定要链接的库文件。例如,-lm 表示链接数学库(libm)。
  13. -Wl,: 用于向链接器传递选项。例如,-Wl,-rpath,/path/to/lib 可以设置运行时库搜索路径。
  14. -D: 定义宏。相当于在源代码中使用 #define 指令定义一个宏。

3.4 Linux调试器-gdb使用

gdb 是 GNU Debugger 的缩写,是一个用于调试程序的强大工具。它允许开发者在运行中的程序中进行追踪、检查变量、查看堆栈和内存,并在必要时对程序进行修改。gdb 支持多种编程语言,包括 C、C++、Objective-C、Fortran、Go 等

3.4.1 背景

  • 程序的发布方式有两种,debug模式和release模式
  • Linux gcc/g++出来的二进制程序,默认是release模式
  • 要使用gdb调试,必须在源代码生成二进制程序的时候, 加上-g选项

例如,使用 GCC 编译 C 源文件时,可以这样使用 -g 选项:

gcc -g source.c -o program

使用 G++ 编译 C++ 源文件时,同样可以添加 -g 选项:

g++ -g source.cpp -o program

查看程序是否是debug版本:

readelf -S mycode | grep -i debug

这条命令的作用是查找 ELF 文件中包含 “debug” 字符串的节信息。通常情况下,如果 ELF 文件包含调试信息,这些信息可能会存储在特定的节中,而这个命令就是用来查找这些包含调试信息的节。

3.4.2 使用方法

  1. list (l) [行号]:在当前源代码位置开始,显示源代码的 10 行,并将当前位置向下移动 10 行。如果提供了行号参数,则从指定行号开始显示源代码。
  2. list (l) [函数名]:显示指定函数的源代码。
  3. run (r):运行程序。
  4. next (n):单步执行程序,逐行执行,但是如果当前行是一个函数调用,则直接执行完整个函数。
  5. step (s):单步执行程序,逐语句执行,如果当前行是一个函数调用,则进入函数体内部继续执行。
  6. break (b) [行号]:在指定行号设置断点。
  7. break (b) [函数名]:在指定函数的入口处设置断点。
  8. info breakpoints:查看当前设置的所有断点信息。
  9. finish:执行到当前函数的返回点,然后挂起等待下一个命令。
  10. print (p) [表达式]:打印表达式的值,可以通过表达式修改变量的值或者调用函数。
  11. print (p) [变量名]:打印变量的值。
  12. set var:修改变量的值。
  13. continue (c):从当前位置开始连续执行程序,直到遇到下一个断点或程序结束。
  14. delete [number]:删除指定编号的断点。
  15. delete [file:number]:删除指定文件和行号的断点。
  16. delete [file:function]:删除指定文件和函数名的断点。
  17. delete [function]:删除指定函数的断点。
  18. disable [number]:禁用指定编号的断点。
  19. disable [file:number]:禁用指定文件和行号的断点。
  20. disable [file:function]:禁用指定文件和函数名的断点。
  21. disable [function]:禁用指定函数的断点。
  22. enable breakpoints:启用所有断点。
  23. info breakpoints:查看当前设置的所有断点信息。
  24. display [变量名]:跟踪查看指定变量,每次程序停下来都会显示它的值。
  25. undisplay:取消对先前设置的变量的跟踪。
  26. until [X行号]:执行程序直到达到指定行号,常用于跳出循环。
  27. breaktrace (bt):查看各级函数调用及参数。
  28. info locals (i locals):查看当前栈帧局部变量的值。
  29. quit:退出 GDB 调试器。

3.5 Linux项目自动化构建工具-make/Makefile

Make 是一个命令行工具,用于执行 Makefile 文件中定义的规则。而 Makefile 是一个文本文件,其中包含了一系列规则、变量和命令,用于描述项目的结构、依赖关系和构建过程。

Make 工具通过读取 Makefile 文件,并根据其中的规则来执行相应的命令,实现自动化的编译和构建过程。

3.5.1 基本结构

Makefile 的基本结构如下:

target: dependencies
    command
  • target 表示规则的目标文件,可以是一个目标文件(如可执行文件)或是一个伪目标(如 clean)。
  • dependencies 表示目标文件所依赖的文件或其他目标文件。
  • command 是生成目标文件的命令,可以是一条或多条 Shell 命令。

示例:

mybin: code.c
	gcc code.c -o mybin
.PHONY:clean
clean:
	rm -f mybin

注意: 在 Makefile 中,命令部分必须使用 Tab 键进行缩进,否则可能导致解析错误。

在 Makefile 中,.PHONY 是一个特殊的目标标记,用于声明一个伪目标(phony target)。所谓伪目标,指的是在 Makefile 中定义的目标,但并不对应真实的文件名,而是用于执行一些命令或者其他操作的标记。

Make 工具会比较目标文件和其依赖文件的时间戳,如果依赖文件的时间戳比目标文件的时间戳更新,或者目标文件不存在,那么 Make 工具会认为目标文件需要重新生成。而这些时间戳就是文件的 AMC 时间属性,在文件系统中记录着文件的访问、修改和状态改变的时间。

makefile可以建立多层依赖关系

示例:

Test : test.o
   gcc test.o -o Test 
test.o : test.s
   gcc -c test.s -o test.o
test.s : test.i
   gcc -S test.i -o test.s
test.i : Test.c
   gcc -E Test.c -o test.i
Test:Add.o Div.o Mul.o Sub.o TestMain.o
	gcc -o $@ $^
%.o:%.c
	gcc -c $<
.PHONY:clean
clean:
	rm -f *.o Test
  1. Test:Add.o Div.o Mul.o Sub.o TestMain.o: 这一行指定了编译的目标(target),即生成名为 “Test” 的可执行文件。依赖项(prerequisites)包括 Add.o、Div.o、Mul.o、Sub.o 和 TestMain.o,它们是源文件编译生成的目标文件。
  2. gcc -o $@ $^: 这是一个规则(rule),用于生成目标文件 “Test”。$@ 是一个自动化变量,表示目标文件,$^ 是另一个自动化变量,表示所有的依赖项。这行命令的意思是使用gcc编译器将所有依赖项链接成一个可执行文件。
  3. %.o:%.c: 这是另一个规则,用于生成目标文件。它告诉Make如何将.c文件编译成.o文件。"%”表示通配符,匹配所有的.c文件。
  4. gcc -c $<: 这是编译.c文件为.o文件的命令。“$<” 是一个自动化变量,表示第一个依赖项,即源文件。"-c"选项告诉gcc只编译,而不链接,生成目标文件。
  5. .PHONY:clean: 这一行指定了一个伪目标(phony target)“clean”,它不是一个真正的文件名,而是用于执行清理操作的一个标签。
  6. clean: rm -f *.o Test: 这是一个规则,用于清理生成的目标文件和可执行文件。“rm -f” 是删除文件的命令, “*.o” 匹配所有的目标文件,“Test” 是可执行文件名。

3.5.2 变量

在 Makefile 中,变量和函数是非常有用的功能,它们可以增强 Makefile 的灵活性和可维护性。让我来介绍一下 Makefile 中的变量和函数:

变量(Variables):

  1. 定义变量: 可以使用 变量名 = 值 的形式来定义变量,例如:

    codeCC = gcc
    CFLAGS = -Wall -O2
    
  2. 使用变量: 在 Makefile 中,可以使用 $(变量名) 或者 ${变量名} 的方式来引用变量,例如:

    cc=gcc
    src=code.c
    target=mybin
    
    $(target):$(src)
    	$(cc) $(src) -o $(target)
    .PHONY:clean
    clean:
    	rm -f $(target)
    
  3. 预定义变量: Make 工具预定义了一些特殊的变量,如 $@ 表示目标文件,$^ 表示所有依赖文件,$< 表示第一个依赖文件等。

cc=gcc
src=code.c
target=mybin

$(target):$(src)
	$(cc) $^ -o $@
.PHONY:clean
	rm -f $(target)
  1. 多行变量: 可以使用反斜杠 \ 来表示多行变量,例如:
codeSOURCES = \
    source1.c \
    source2.c \
    source3.c
  1. 不同的编译器
.PHONY:all
all:mytest myprocess
mytest:mytest.cc
    g++ -o $@ $^ -std=c++11                                                                                           myprocess:myprocess.c
    gcc -o $@ $^ -std=c99
.PHONY: clean
clean:  
    rm -f mytest myprocess 

3.5.3 特殊符号

@

在 Makefile 中,@ 符号可以用来控制命令的显示。当命令行以 @ 开头时,Make 工具在执行时不会显示该命令,这在 Makefile 中通常用于隐藏一些执行细节,使输出更加简洁清晰。

hello: hello.o
    @echo "Linking $@"  # 这个命令不会显示
    $(CC) $(CFLAGS) -o $@ hello.o

# 清理操作
clean:
    @echo "Cleaning..."  # 这个命令不会显示
    rm -f hello hello.o

#注释

3.6 Linux小程序-进度条

3.6.1 C/C++缓冲区

在 C/C++ 中,标准输出流 stdout 是一个缓冲输出流,它指向标准输出设备,通常是显示器或控制台窗口。当使用 printf() 函数进行输出时,默认情况下输出会先暂时存储在 stdout 的输出缓冲区中,并不会立即显示在屏幕上,直到缓冲区满了或者显式地刷新缓冲区时才会将内容输出到屏幕上。

因此,当你调用 printf() 输出内容时,输出的文本会被放入 stdout 的缓冲区中,并不会立即显示在屏幕上,而是等待缓冲区满了或者程序结束时才会显示。

你可以通过调用 fflush(stdout) 函数来刷新 stdout 的缓冲区,这样会强制将缓冲区中的内容立即输出到屏幕上。

如果想要在输出中触发缓冲区的刷新,可以使用以下方法:

  1. 显示调用 fflush() 函数

    printf("This will be immediately flushed\n");
    fflush(stdout); // 刷新标准输出缓冲区
    
  2. 在输出字符串中包含 '\n' 或者 '\r'

    \n\r` 是用来控制换行和回车的转义字符。

    printf("This will be immediately flushed\n"); // 自动刷新
    
  3. 使用 stderr 输出错误信息: 标准错误流 stderr 是不经过缓冲的,因此输出到 stderr 的信息会立即显示在终端上,不需要显示调用 fflush()

    fprintf(stderr, "This will be immediately flushed\n"); //自动刷新
    

需要注意的是,上述方法只会刷新对应的缓冲区,而不会刷新其他流的缓冲区。如果想要确保所有输出立即刷新到终端,需要分别对每个流调用 fflush() 函数。

3.6.2 分文件编写

# makefile
process:process.c main.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f $@  
// process.h
#pragma once
#include <stdio.h>

void process();
// process.c
#include "process.h"
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>

#define SIZE 101
#define STYLE '/'                                                                                                           
void process()
{
    const char* lable = "|/-\\";
    char bar[SIZE];
    memset(bar,'\0',sizeof(bar));

    int i =0;
    while(i<=100)
    {
        printf("[%-100s] [%d%%][%c]\r",bar,i,lable[i%4]);

        fflush(stdout);
        bar[i++] = STYLE;

        //每过一秒加载一次
        sleep(1);
    }

    printf("\n");
}
// main.c
#include "process.h"
int main()
{
    process();
    return 0;                                                 
}

3.7 Linux中使用git

在github上创建一个项目,复制项目的链接,以备接下来进行下载.

创建好一个放置代码的目录.

git clone url
  1. git add

将代码放到刚才下载好的目录中,

git add filename

将需要用 git 管理的文件告知 git

  1. git commit

提交改动到本地

git commit -m "说明"

-m 后面的内容是做出对本次提交的说明

提交的时候应该注明提交日志,描述改动的详细内容。

  1. git push

同步到远端服务器上

git push

需要填入用户名密码。同步成功后, 刷新 Github 页面就能看到代码改动了。

注意:github 的认证策略发生了改变,在 2021年8月13日 的时候,用户名加密码的认证方式被去掉了,换成了 **个人令牌(Personal Access Token)**的校验方式。

Managing your personal access tokens - GitHub Docs

密码是你的个人令牌

第一次使用要先配置你的账号和邮箱:

git config --global user.name "username"
git config --global user.email "email@example.com"

4. 冯诺依曼体系结构

1

截至目前,我们所认识的计算机,都是有一个个的硬件组件组成

  • 输入单元:包括键盘, 鼠标,扫描仪, 写板等
  • 中央处理器(CPU):含有运算器和控制器等
  • 输出单元:显示器,打印机等

4.1 统一的存储器

冯·诺依曼体系结构的关键特点在于其统一的存储器概念,这里指的存储器是内存。以下是必须强调的几点:

  1. 存储器指的是内存: 冯·诺依曼体系结构中的存储器是指内存,即计算机的主存储器。程序指令和数据都存储在内存中,CPU通过读取和写入内存来执行程序和处理数据。
  2. CPU只能对内存进行读写: 在冯·诺依曼体系结构中,CPU只能直接与内存进行交互,而不能直接访问外设(输入或输出设备)。所有的指令和数据都必须经过内存进行传输,CPU通过内存读取指令和数据,执行运算,并将结果写回内存。
  3. 外设与内存交互: 所有的外设(输入或输出设备)与内存之间的数据传输都必须经过内存。外设向计算机输入数据时,数据首先被写入内存,然后CPU从内存中读取数据进行处理;而当计算机需要向外设输出数据时,CPU首先将数据写入内存,然后外设从内存中读取数据。
  4. 所有设备只能直接与内存打交道: 冯·诺依曼体系结构中,所有的计算机组件(包括CPU和外设)都只能直接与内存进行交互,而不能直接相互通信。这种设计简化了计算机系统的结构,使得程序执行和数据传输更加统一和高效。

冯·诺依曼体系结构的重要性在于它提供了一种统一的框架,将计算机硬件和软件统一起来,使得程序可以被存储在内存中并按顺序执行。在冯·诺依曼体系结构中,CPU 只能直接与内存进行交互,而所有的输入和输出设备也都需要通过内存来进行数据传输。现在让我们通过一个实例来深入理解数据在计算机中的流动过程:

假设你登录 QQ 并与某位朋友进行聊天:

  1. 登录 QQ:
    • 当你打开 QQ 应用程序时,操作系统从硬盘读取 QQ 程序的代码和相关数据到内存中。
    • 你输入用户名和密码,键盘输入的数据被发送到内存中,然后由操作系统处理并传递给 QQ 程序。
  2. 与朋友聊天:
    • 当你打开聊天窗口并开始发送消息时,你输入的消息通过键盘输入到内存中。
    • QQ 程序检测到新消息后,将消息从内存读取出来,然后处理并发送给 QQ 服务器。
    • QQ 服务器接收到消息后,将其存储在内存中,并通过网络传输发送给你的朋友的设备。
    • 朋友的设备接收到消息后,将消息存储在内存中,然后由 QQ 应用程序读取并显示在聊天窗口中。
  3. 接收消息:
    • 当你的朋友发送消息给你时,消息首先被存储在 QQ 服务器的内存中。
    • 你的设备定期从 QQ 服务器读取新消息,并将其存储在内存中。
    • QQ 应用程序检测到新消息后,将消息从内存读取出来,并显示在聊天窗口中。
  4. 发送文件:
    • 当你想发送文件时,你选择要发送的文件并将其从硬盘读取到内存中。
    • QQ 程序将文件数据从内存中读取出来,并发送给 QQ 服务器。
    • QQ 服务器接收到文件数据后,将其存储在内存中,并通过网络传输发送给你的朋友的设备。
    • 朋友的设备接收到文件数据后,将其存储在内存中,然后由 QQ 应用程序读取并保存到硬盘中。

总之,无论是发送消息还是发送文件,数据在计算机中的流动都是通过内存进行的。所有的输入和输出设备都需要将数据存储在内存中,然后由 CPU 进行处理和传输。这种数据流动的方式符合冯·诺依曼体系结构的基本原理。

5. 操作系统

5.1 主要功能

操作系统(Operating System,简称OS)是一种系统软件,是计算机系统中最基本、最核心的部分,负责管理和控制计算机硬件资源,并提供各种服务和接口供应用程序使用。操作系统的主要功能包括以下几个方面:

  1. 资源管理: 操作系统负责管理计算机系统的各种硬件资源,包括中央处理器(CPU)、内存、硬盘、输入输出设备(如键盘、鼠标、显示器、打印机等)等。资源管理的目标是实现对资源的有效利用,提高系统的性能和可靠性。
  2. 进程管理: 操作系统负责管理系统中的进程(或任务),包括进程的创建、调度、同步、通信和销毁等。进程管理使得多个程序可以同时运行,并且能够协调它们之间的执行顺序和资源访问。
  3. 内存管理: 操作系统负责管理计算机系统的内存,包括内存的分配、回收、存储器保护和虚拟内存等。内存管理的目标是为各个程序提供合适的内存空间,并且确保它们之间不会相互干扰。
  4. 文件系统: 操作系统提供文件系统来管理计算机系统中的文件和目录,包括文件的创建、读写、删除、复制和移动等操作。文件系统使得用户可以方便地组织和管理自己的数据,并且可以通过文件进行数据共享和交换。
  5. 设备管理: 操作系统负责管理计算机系统中的各种输入输出设备,包括设备的初始化、驱动程序的加载、设备的控制和数据传输等。设备管理使得应用程序可以方便地与硬件设备进行通信和交互。
  6. 用户界面: 操作系统提供用户界面,使得用户可以通过图形界面(GUI)或命令行界面(CLI)与计算机进行交互。用户界面使得用户可以方便地操作计算机,并且可以通过操作系统提供的各种命令和工具来完成各种任务。

5.2 主要目的

  1. 管理硬件资源: 操作系统负责管理计算机系统中的各种硬件资源,包括中央处理器(CPU)、内存、输入/输出设备(如键盘、鼠标、打印机)、存储设备(如硬盘、固态硬盘)、网络接口等。操作系统通过提供统一的接口和资源分配机制,使得各种硬件设备能够有效地协同工作,并为应用程序提供必要的硬件支持。
  2. 管理软件资源: 操作系统管理计算机系统中的软件资源,包括应用程序、系统程序、驱动程序、库文件等。操作系统负责加载、执行和卸载应用程序,以及管理应用程序之间的资源共享和通信。此外,操作系统还负责管理系统的各种系统服务和进程,如进程管理、内存管理、文件系统管理等。
  3. 提供良好的执行环境: 操作系统为用户和应用程序提供一个友好的执行环境,使得用户能够方便地使用计算机系统,并且能够高效地运行应用程序。操作系统通过提供图形用户界面(GUI)或命令行界面(CLI)、多任务处理、文件管理、网络支持等功能,使得用户能够轻松地进行各种操作,如文件操作、网络通信、程序运行等。
  4. 实现资源的抽象和保护: 操作系统通过对硬件资源的抽象和保护,为用户和应用程序提供了一个统一的编程接口和执行环境。通过对硬件资源进行抽象,操作系统隐藏了底层硬件的细节,使得用户和应用程序能够以统一的方式访问和管理各种硬件设备。同时,操作系统通过提供访问控制和安全机制,保护系统的资源不受未经授权的访问和恶意攻击。

5.3 定位

在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件

计算机管理硬件
先描述起来,用struct结构体;
再组织起来,用链表或其他高效的数据结构 ;

2

  1. 用户部分: 用户是计算机系统的最终使用者,可以进行指令操作、开发操作和管理操作。用户通过与计算机系统交互,执行各种任务和操作。
  2. 用户操作接口: 用户通过操作系统提供的用户操作接口来与系统进行交互。这包括命令行 Shell 外壳、库函数和部分指令。用户可以通过这些接口来执行操作系统提供的功能,如文件操作、进程管理、网络通信等。
  3. 系统软件部分: 系统软件是操作系统及其相关组件的集合,负责管理和控制计算机系统的各种资源和功能。其中,系统调用是操作系统提供给用户程序的接口,用于访问操作系统核心功能和资源。操作系统则包括内存管理、进程管理、文件管理、驱动管理等子系统,管理和调度系统中的各种资源和任务。
  4. 驱动程序: 驱动程序是连接操作系统和底层硬件之间的桥梁,负责管理和控制硬件设备。例如,网卡驱动程序负责管理计算机的网络接口卡,硬盘驱动程序负责管理计算机的硬盘等。驱动程序与操作系统交互,通过系统调用来访问操作系统提供的功能,以实现对硬件设备的控制和管理。
  5. 底层硬件: 底层硬件是计算机系统的物理部分,包括网卡、硬盘和其他外设等。这些硬件设备提供了计算机系统的基本功能和服务,如数据存储、数据传输、输入输出等。底层硬件通过驱动程序与操作系统进行交互,使得操作系统和用户能够有效地管理和利用硬件资源。

5.4 系统调用和库函数

  1. 系统调用(System Call): 系统调用是操作系统提供给用户程序或应用程序的一组接口,用于访问操作系统核心功能和资源。系统调用是用户空间程序与操作系统内核之间的桥梁,通过系统调用,用户程序可以请求操作系统执行特定的任务,如文件操作、进程管理、网络通信等。系统调用提供了一种安全和受控的方式,让用户程序可以访问操作系统的功能,同时也提供了一种隔离机制,保护操作系统核心不受用户程序的恶意行为影响。
  2. 库函数(Library Function): 库函数是一组预先编写好的函数集合,封装了常用的功能和算法,供用户程序或应用程序调用和使用。库函数通常由开发者编写或者是操作系统或第三方提供,并以库的形式存储在文件中。库函数可以包含在静态库(.a)或动态库(.so)中,用户程序在编译或运行时链接到这些库中,以获取所需的功能和服务。库函数的使用可以大大简化编程任务,提高开发效率,同时也有利于代码的重用和维护。

5.5 总结

在狭义上,操作系统通常指的是内核(kernel),即计算机系统的核心部分,负责管理和控制硬件资源,并提供基本的系统服务。Linux 内核是一个开源的 Unix-like 操作系统内核,是 Linux 操作系统的核心组件,负责管理计算机系统的各种硬件资源,如处理器、内存、文件系统等。

在广义上,操作系统除了内核之外,还包括用户空间的一些组件,如 shell(命令行解释器)、系统工具、应用程序等。Shell 是用户与操作系统之间的交互界面,用户可以通过 Shell 发出命令来与系统进行交互和控制。在 Linux 系统中,常用的 Shell 包括 Bash、Zsh、Fish 等,它们提供了丰富的命令和功能,方便用户管理文件、执行程序、配置系统等操作。

操作系统(OS): 操作系统是计算机系统的核心软件,负责管理和控制硬件资源,并提供基本的系统服务。在广义上,操作系统包括内核、shell、系统调用和库等组件。

内核(Kernel): 内核是操作系统的核心部分,负责直接管理硬件资源,如处理器、内存、设备等。Linux 内核是一个开源的 Unix-like 操作系统内核,是 Linux 操作系统的核心组件。

Shell: Shell 是用户与操作系统之间的交互界面,允许用户通过命令行或图形界面与操作系统进行交互和控制。常见的 Shell 包括 Bash、Zsh、Fish 等。

系统调用(System Call): 系统调用是操作系统提供给用户空间程序访问内核功能的接口,允许用户程序请求操作系统执行特权操作,如文件操作、进程管理等。

库(Library): 库是一组预先编写好的程序代码的集合,可供多个程序共享和重复使用。库包括系统库和用户自定义库,提供各种功能和功能模块,方便开发者开发和部署应用程序。

文章目录

6. 进程

https://mirrors.edge.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.19.tar.gz Linux内核源代码

6.1 进程的基本概念

**课本概念:**进程是计算机中正在执行的程序的实例。

**书中概念:**进程是操作系统对一个正在运行的程序的一种抽象

内核观点:从操作系统内核的角度来看,进程是内核管理的基本单位,负责分配系统资源(如CPU时间、内存等)。内核为每个进程分配一个进程控制块(PCB),用于保存进程的状态信息和资源分配情况。

6.2 PCB

PCB是指进程控制块(Process Control Block),也称为进程描述符(Process Descriptor),是操作系统中用于管理和控制进程的重要数据结构之一。每个进程都有一个对应的PCB,它记录了操作系统对该进程的管理和控制所需的各种信息。

在Linux操作系统中,进程信息被存储在一个称为task_struct的数据结构中,它是Linux内核中表示进程的主要数据结构之一,相当于课本中提到的进程控制块(PCB)

进程控制块(task_struct)的内容:

  1. 标识符:用于标识进程的唯一标识符,通常是一个数字,被操作系统用来区分不同的进程。
  2. 状态:描述进程的当前状态,如运行、就绪、阻塞等,以及进程的退出代码和退出信号等。
  3. 优先级:指定了进程相对于其他进程的执行优先级,操作系统根据优先级调度进程执行。
  4. 程序计数器:保存了进程即将执行的下一条指令的地址,是进程中正在执行指令的位置指示器。
  5. 内存指针:包括指向进程代码和相关数据的指针,还可能包括指向共享内存区域的指针,这些内存区域可以由多个进程共享。
  6. 上下文数据:包括了进程在执行时CPU寄存器中的数据,这些数据是进程上下文的一部分,用于在进程切换时保存和恢复现场。
  7. I/O状态信息:包括了进程当前的I/O请求、分配给进程的I/O设备、以及进程正在使用的文件列表等。
  8. 记账信息:包括了关于进程资源使用情况的统计信息,如处理器时间总和、时钟周期总和、时间限制等。
  9. 其他信息:可能包括进程的其他相关信息,如进程所属用户、父进程ID、子进程列表等。

Linux内核中的进程控制块是通过指向进程的指针来实现的。

具体来说,每个进程控制块(task_struct)都包含指向其他相关进程控制块的指针。例如,子进程的进程控制块可能包含指向父进程的指针,以及指向其兄弟进程或其他相关进程的指针。这些指针的存在使得在进程之间进行导航和通信变得更加容易和高效。

因此,虽然Linux内核中的进程控制块不是严格意义上的链表节点,但是通过指针之间的链接,可以形成类似链表的结构,从而实现对进程之间关系的有效管理和维护。

(struct task_struct*)(int)&list -(int)&list-(int)&(task_struct*)0->list)

计算结构体成员的偏移量,以便在编程中更灵活地访问结构体成员。

对进程的管理主要涉及对进程控制块(PCB)的增删查改操作。

6.3 查看进程

通过系统调用获取进程标示符

#include<stdio.h>
#include<unistd.h>
int main()
{
    int i=0;
    while(i<=100)
    {
        pid_t id=getpid();
        pid_t p_id=getppid();
        printf("I am a process, my pid is:%d, my parent id is %d\n",id    ,p_id);                                                               
        sleep(1);
    }
}

右键shell,复制ssh渠道,在另一个终端中输入命令

while :; do ps axj | head -1 && ps axj |grep myprocess|grep -v "grep";sleep 1;echo "##################"; done

可以监视这个进程

linux 中创建进程的方式:

  1. 命令行中直接启动进程 – 手动启动
  2. 通过代码来进程创建

启动进程,本质就是创建进程,一般是通过父进程创建的!

我们命令行启动的进程,都是bash的子进程

通过 /proc 系统文件夹查看

ls /proc

  • /proc/<PID>/cwd 表示进程的当前工作目录(current working directory),它是一个符号链接,指向进程当前工作的目录的路径。
  • /proc/<PID>/exe 是一个符号链接,指向进程正在执行的可执行文件的路径。

6.4 fork

#include<stdio.h>
#include<sys/types.h>
int main()
{
    pid_t ret = fork();

    if(ret==0)
    {
        // 子进程
        while(1)
        {
            printf("我是子进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else 
    {
        // 父进程
        while(1)
        {
            printf("我是父进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
            sleep(1);
        }
    }

    return 0;
}

fork() 是一个在 Unix 和类 Unix 系统中常见的系统调用,用于创建一个新的进程(子进程)。具体来说,fork() 调用会创建当前进程的一个副本,并在父子进程之间返回不同的值。新创建的子进程与父进程几乎完全相同,包括程序的代码、数据和上下文等。但是,子进程会在调用 fork() 后继续执行,而父进程则继续在原处执行。

fork() 的一般步骤如下:

  1. 调用 fork() 后,操作系统会创建当前进程的一个副本。
  2. 在父进程中,fork() 返回子进程的进程ID(PID),在子进程中,fork() 返回 0。
  3. 父子进程都会继续执行 fork() 调用后的代码,但可以通过返回值的不同来区分它们。

在调用 fork() 后,父子进程会共享一些资源,如代码段、全局变量等,但它们会拥有各自独立的地址空间和内存副本。这意味着如果一个进程在内存中修改了数据,这个修改不会影响到另一个进程。因此,fork() 调用后的子进程通常会执行 exec() 系列函数加载另一个程序映像,以便执行新的任务。

问题:

1.fork干了什么事?

fork() 系统调用会创建当前进程的一个副本,包括程序的代码、数据和上下文等。具体来说,它会复制当前进程的内存映像,然后在父进程和子进程之间分别返回不同的值,并让两者继续执行。

2.为什么fork有两个返回值

fork() 有两个返回值是因为它同时要返回父进程和子进程的状态。对于父进程,返回新创建的子进程的进程ID(PID),以便父进程知道它的子进程是谁;而对于子进程,返回0,表示子进程的起始状态。这样父子进程可以通过返回值的不同来区分彼此。

3.为什么会给父进程返回子进程pid,给子进程返回0?

fork() 给父进程返回子进程的PID,给子进程返回0,是为了方便父子进程在继续执行后能够识别自己的角色。父进程可以根据子进程的PID来跟踪和管理子进程,而子进程则可以根据返回值是否为0来确定自己是子进程。

4.fork之后,父子进程谁先运行?

fork() 后,父子进程的执行顺序是不确定的,取决于操作系统的调度算法。通常情况下,父子进程的执行顺序是不确定的,取决于操作系统的调度算法。通常情况下,父进程和子进程的执行是并发的,谁先运行取决于内核的调度策略和运行环境的情况。在大多数情况下,父子进程的执行顺序是不确定的。

5.如何理解同一个变量,会有不同的值?

同一个变量在父进程和子进程中会有不同的值,这是因为在 fork() 后,父进程和子进程拥有各自独立的地址空间和内存副本。因此,对于每个进程来说,它们的变量值是保存在自己的内存空间中的,并且相互之间不会影响。修改父进程中的变量不会影响到子进程中的变量,反之亦然。

写实拷贝:
在调用 fork() 函数时,操作系统并不会立即复制整个进程的内存空间。相反,它使用了写时复制(Copy-On-Write)的技术,该技术延迟了内存的复制过程,只有在子进程或父进程修改了共享的内存页时,才会进行实际的复制。

具体来说,当调用 fork() 时,操作系统会将父进程的地址空间标记为只读,并告诉子进程它共享父进程的地址空间。这意味着子进程和父进程会共享相同的物理内存,直到其中一个进程尝试修改内存中的内容。当其中一个进程试图修改共享内存时,操作系统会将该页复制到新的物理内存页上,使得修改只影响到修改发起的进程,而不会影响其他进程。

通过写时复制技术,操作系统能够有效地节省内存开销,因为只有在需要修改共享内存时才会进行实际的复制,而不是在 fork() 调用时立即复制整个地址空间。这样可以使得 fork() 的性能更高,并且减少了进程间的内存消耗。

6.5 运行状态

常见的进程状态包括:

  1. 就绪(Ready):进程已经准备好运行,但是由于CPU资源被其他进程占用或者处于等待某些资源的状态而暂时无法执行。
  2. 运行(Running):进程正在CPU上执行指令,处于活跃状态。
  3. 阻塞(Blocked):进程由于等待某种事件(如I/O操作、信号等)而暂时无法执行,处于阻塞状态。一旦事件发生,进程会变为就绪状态。
  4. 创建(New):进程正在被创建,尚未分配到资源。
  5. 终止(Terminated):进程已经执行完毕并结束,或者由于某种原因被系统终止。

进程的状态转换通常由操作系统的调度器来管理,根据系统资源的分配和进程的需求,调度器会将进程从一个状态转换到另一个状态,以实现资源的最优利用和任务的合理分配。

6.5.1 运行状态

在操作系统中,运行状态通常是指进程在CPU的运行队列中处于活跃执行状态。当一个进程处于运行状态时,它正在CPU上执行指令,即它的指令序列正在被处理器执行。这意味着操作系统已经将该进程从就绪队列中选择出来,并将其分配给CPU执行。

在多任务系统中,CPU维护一个运行队列,其中包含可以立即执行的进程。这些进程通常是根据调度算法选择的,以确保公平性、效率和其他性能指标。进程从就绪状态进入运行状态,然后根据操作系统的调度策略执行一段时间,最终可能会被挂起或者被抢占,让其他进程执行。

因此,运行状态是指处于CPU运行队列中、正在执行指令的进程状态。

6.5.2 就绪状态

就绪状态通常表示进程已经准备好运行,但是由于CPU资源被其他进程占用,所以暂时无法执行。在这种情况下,进程会被放置在操作系统维护的就绪队列中等待调度。就绪队列是一个数据结构,用于存储已经准备好运行但尚未被分配到CPU执行的进程。一旦CPU空闲,操作系统的调度器就会从就绪队列中选择一个进程,并将其调度到CPU上执行。

6.5.3 阻塞状态

阻塞状态通常指进程在等待外部事件(如I/O操作)完成时暂时无法继续执行,因此进程会被置于阻塞状态。在这种情况下,进程所处的等待队列通常是由设备或外部资源所维护的,用于管理等待该资源完成的进程。

当进程执行一个需要等待外部资源完成的操作时(比如从磁盘读取数据或等待用户输入),它会被置于阻塞状态,并被添加到相应设备的等待队列中。一旦外部事件完成,操作系统会将进程移出阻塞状态,转换为就绪状态,然后根据调度算法决定是否将其重新放入运行队列中执行。

6.5.4 挂起状态

挂起状态通常是指操作系统将进程的内存数据(或整个进程)置换到外部存储设备(如硬盘)以释放内存资源,以应对内存资源不足的情况。在这种情况下,挂起的进程被暂时移出内存,并且其内存数据会被交换到外部设备中,从而释放内存空间以供其他进程使用。

当操作系统面临内存资源不足的情况时,会优先挂起一些阻塞的进程,因为这些进程已经暂时无法继续执行,所以将它们置换出内存不会对系统的整体性能产生显著影响。这些被挂起的进程的内存数据会被交换到硬盘或其他外部存储设备中,并且操作系统会在必要时将它们重新加载到内存中,以便在之后被调度执行。

挂起状态通常用于操作系统的内存管理策略中,以确保系统能够有效地利用有限的内存资源,并为正在执行的进程提供足够的内存空间。

swap分区

虽然可以在计算机系统中配置一个或多个swap分区(或者称为交换分区),但是设置过大的swap分区也可能会带来一些问题。

以下是设置过大swap分区可能导致的一些问题:

  1. 硬盘空间浪费:过大的swap分区可能会占用大量的硬盘空间,这些空间有时候可能并不是必需的,而且是浪费的。
  2. 性能影响:过大的swap分区可能会导致频繁的磁盘读写操作,这会对系统的性能产生负面影响。因为频繁的磁盘访问通常比内存访问慢得多。
  3. SSD寿命影响:如果使用的是固态硬盘(SSD),频繁的swap操作可能会缩短SSD的寿命,因为SSD的写入次数是有限的。
  4. 内存管理不足:过大的swap分区有时可能会导致系统过度依赖swap,而不是更有效地利用物理内存。这可能会掩盖内存管理问题,并导致性能下降。

因此,通常建议将swap分区大小设置为物理内存的一部分,以提供一定程度的内存扩展和备份,但同时也需要避免设置过大的swap分区,以免造成不必要的问题。具体的最佳设置大小取决于系统的具体用途和配置。

6.5.5 状态维护的本质

  1. 更改 PCB(进程控制块)状态的整数变量:PCB是操作系统用来管理进程的数据结构,其中包含了进程的各种信息,包括进程的状态。通过更改PCB中的状态字段,操作系统能够跟踪和管理进程的状态变化。这个状态字段通常是一个整数变量,每个状态都对应一个特定的值,比如就绪、运行、阻塞等。通过更新这个整数变量,操作系统可以记录进程当前的状态,并且在需要的时候进行状态转换。
  2. 将 PCB 放入不同的队列中:为了有效地管理进程,操作系统通常会维护多个队列,用于存储处于不同状态的进程。例如,就绪队列用于存储已经准备好运行但尚未分配到CPU的进程,阻塞队列用于存储由于等待某些资源而暂时无法执行的进程等等。当进程的状态发生变化时,操作系统会将相应的 PCB 从一个队列移到另一个队列中,以反映其新的状态。这样做不仅有助于操作系统有效地组织和调度进程,还可以提高系统的性能和资源利用率。

操作系统中,进程的状态管理主要涉及到进程控制块(PCB),PCB 包含了操作系统用于管理进程的所有必要信息,包括进程的状态、进程的代码和数据的地址、寄存器状态、优先级等等。进程的代码和数据通常存储在内存中,而 PCB 存储在操作系统的内核数据结构中,它们是分开的。

所有与进程的状态相关的操作,如进程的创建、调度、挂起、唤醒、终止等,都是通过操作进程的 PCB 来完成的。当进程状态发生变化时,操作系统会更新 PCB 中的状态字段,并且根据需要将 PCB 移动到相应的队列中。

进程的代码和数据本身不直接参与进程状态的管理。它们由操作系统加载到内存中,供进程在执行时使用,但不影响操作系统对进程状态的管理。因此,所有与进程状态相关的操作都只涉及到 PCB,与进程的代码和数据无关。

状态维护的本质

1.更改pcb status的整数变量

2.将pcb放入不同的队列中

我们所说的所有的过程,都只和进程的pcb有关,和进程的代码和数据没有关系。

6.6 Linux内核源代码进程状态

6.6.1 源代码定义

下面的状态在kernel源代码里定义:

static const char * const task_state_array[] = {R (running), // 0S (sleeping), // 1D (disk sleep), // 2T (stopped), // 4t (tracing stop), // 8X (dead), // 16Z(zombie), // 32
};

当进程处于不同的状态时,这些状态描述如下:

  1. R (running):表示进程正在运行。这意味着进程当前正在使用 CPU 执行指令,或者已经准备好被 CPU 执行。
  2. S (sleeping):表示进程正在休眠。这意味着进程正在等待某个事件的发生,比如等待 I/O 操作完成,等待信号,或者在内核调度器的等待队列中等待 CPU 被分配。
  3. D (disk sleep):表示进程正在磁盘休眠。这是一种特殊的睡眠状态,通常发生在进程等待磁盘 I/O 操作完成的情况下。在这个状态下,进程无法被中断,只能在相关的 I/O 操作完成后恢复。
  4. T (stopped):表示进程已经停止。这意味着进程被暂停了,通常是通过发送 SIGSTOP 信号给进程来实现的。在这个状态下,进程不会继续执行任何指令,但可以通过发送 SIGCONT 信号来继续执行。
  5. t (tracing stop):表示进程处于跟踪停止状态。这是一种特殊的停止状态,通常用于调试目的,表示进程正在被调试器跟踪,处于暂停状态。
  6. X (dead):表示进程已经终止。这个状态表示进程已经完成了它的执行,并且相关的资源已经被操作系统回收或释放。
  7. Z (zombie):表示进程是僵尸进程。这意味着进程已经终止,但是其父进程还没有读取其退出状态。僵尸进程会占用系统资源,直到其父进程调用 wait() 或 waitpid() 等系统调用来获取其退出状态为止。

6.6.2 进程状态查看

ps aux / ps axj 命令

ps auxps axj 是两个常用的 Linux 命令,用于显示系统中当前运行的进程信息。

  1. ps aux

    • ps 是一个用于显示当前进程信息的命令。
    • a 选项表示显示所有用户的所有进程,而不仅仅是当前用户的进程。
    • u 选项表示以用户为中心的输出格式显示进程的详细信息。
    • x 选项表示显示没有控制终端的进程。

    因此,ps aux 命令会显示所有用户的所有进程,并以用户为中心的输出格式显示进程的详细信息,包括进程的 PID、CPU 占用率、内存占用率、运行时间等。

  2. ps axj

    • ps 是用于显示进程信息的命令。
    • a 选项表示显示所有用户的所有进程。
    • x 选项表示显示没有控制终端的进程。
    • j 选项表示以作业格式显示进程的详细信息。

    因此,ps axj 命令会显示所有用户的所有进程,并以作业格式显示进程的详细信息,包括作业 ID、进程组 ID、会话 ID 等。

6.6.3 R (running)

在操作系统中,通常会维护一个运行队列(或称为就绪队列),用于存储处于就绪状态的进程。这些进程已经准备好被 CPU 调度执行,但由于 CPU 资源有限,只有一个进程能够被 CPU 执行。因此,这些进程会被按照一定的调度算法(如先来先服务、最短作业优先等)放置在运行队列中,等待 CPU 的调度。

#include<stdio.h>
#include<unistd.h>
int main()
{
    while(1)
    {
        printf("hello myprocess\n");
    }
    return 0;
}

监视

while :; do ps axj | head -1 && ps axj |grep myprocess|grep -v "grep";sleep 1;echo "##################"; done

监视的时候很难看到R的状态,基本都是S状态。

如果移除 printf 函数的调用,进程就会持续进行 CPU 计算,从而保持在运行状态(R),而不会进入睡眠状态(S)。

这是因为 printf 函数会涉及到标准输出(stdout)的 I/O 操作,即将数据写入到终端。在这个过程中,如果终端的 I/O 操作较慢,进程可能会在 printf 调用期间等待 I/O 操作完成,进入睡眠状态。而如果没有 printf 调用,进程就不会执行这样的 I/O 操作,而是持续进行 CPU 计算,因此保持在运行状态。

前后台进程

其中,**R+**表示正在运行的进程,它可能是前台进程或者后台进程。在这种情况下,+符号指示该进程是前台进程。

在 Linux 中,进程可以分为前台进程和后台进程,这涉及到进程对终端的控制和交互。

前台进程是指与当前终端交互的进程,它们会占用终端并且能够接收终端输入。当你在终端中执行一个命令时,该命令所对应的进程通常会成为前台进程,直到该进程执行完毕或者被暂停。

在 Linux 中,前台进程会处于运行状态(Running),并且会占用终端,因此你无法在终端中继续输入命令,直到该前台进程执行完毕或者被暂停。

值得注意的是,后台进程是指不会占用终端并且不会接收终端输入的进程。你可以在命令行中使用特定的命令或者操作符(如&)将一个进程放入后台运行,这样你就可以在终端中继续输入其他命令而不必等待该进程执行完毕。

./myprocess &

可以用kill -9 pid干掉

###6.6.4 S (sleeping)

在 Linux 中,进程的状态 S (sleeping) 表示进程处于休眠状态,这意味着进程正在等待某些事件发生,比如等待 I/O 操作完成、等待信号、等待锁或者等待其他进程释放资源等。进程处于休眠状态时,它是可中断的,即它可以被操作系统中断,并重新调度。同时,处于休眠状态的进程也会响应外部信号。

在休眠状态下,进程是通过系统调用或者其他事件触发来进入睡眠状态的。一旦等待的事件发生,例如数据读取完成、信号到达等,进程就会被唤醒,并且重新进入就绪状态,等待操作系统的调度器将其调度到 CPU 上执行。

需要注意的是,虽然进程处于休眠状态,但它仍然会对外部信号作出响应。这意味着即使进程处于休眠状态,也可以通过向进程发送信号来触发某些操作,例如终止进程、暂停进程等。

6.6.5 D (disk sleep)

进程处于磁盘休眠状态(Disk sleep)时通常是不可中断的,这意味着操作系统无法直接终止这些进程。这种状态下的进程通常是在等待某些磁盘 I/O 操作完成,例如等待磁盘数据读取或写入完成。

因为这些进程处于必须等待磁盘 I/O 操作完成的情况下,如果操作系统强行终止这些进程,可能会导致数据不一致或损坏,因此通常情况下操作系统不会对这些进程进行强制终止。

在一些情况下,如果进程长时间处于磁盘休眠状态而无法退出,可能会导致系统资源被占用或者系统性能受到影响。在这种情况下,可能需要进行其他方式的干预,例如重启系统或者手动处理导致进程长时间处于磁盘休眠状态的问题。

6.6.6 T (stopped)

在 Linux 中,T(stopped)状态表示进程被暂停了。这种状态通常有几种常见的用途:

  1. 调试: T 状态常用于调试目的。通过发送 SIGSTOP 信号给进程,可以将其暂停,然后可以通过其他调试工具(如 gdb)来检查进程的状态、寄存器的值等,以便诊断和解决问题。
  2. 限制资源访问: 有时候,可以将进程设置为 T 状态来限制其对软件资源的访问。例如,在处理某些敏感数据或者对系统性能敏感的任务时,可以将进程暂停以防止其继续访问资源,直到满足某些条件再恢复执行。
  3. 等待软件资源: 进程可能会在等待某些软件资源时暂停。这种情况下,进程暂停不是由外部信号触发的,而是自发地进入停止状态等待软件资源可用。

在 Linux 中,可以通过发送 SIGSTOP 信号给进程来将其置于 T 状态,进而暂停其执行。而可以通过发送 SIGCONT 信号来恢复进程的执行,使其从停止状态变为就绪状态,等待被调度执行。

kill -l命令用于列出所有可用的信号,包括用于控制进程状态的信号。在默认情况下,kill -l会输出一系列数字,这些数字代表了不同的信号编号。

在 POSIX 标准中,信号编号 18 和 19 分别用于发送 SIGSTOP 和 SIGCONT 信号。这些信号用于控制进程的暂停和继续执行。当向进程发送 SIGSTOP 信号时,该进程会进入 T(stopped)状态,即暂停执行。而当向进程发送 SIGCONT 信号时,该进程会从 T 状态恢复到就绪状态,等待被调度执行。

6.6.7 Z (zombie)

僵尸进程(Zombie)是指一个已经终止,但其父进程尚未调用 wait()waitpid() 等系统调用来获取其终止状态的进程。在进程终止时,操作系统会保留其进程控制块(PCB),将退出状态存储在 PCB 中,并将进程的状态标记为僵尸状态。

在僵尸状态下,进程的代码和数据空间已经被释放,但其 PCB 仍然被操作系统维护。这是因为父进程可能需要查询其子进程的退出状态,以便进行清理或其他处理。只有当父进程调用相关的系统调用来获取子进程的退出状态后,操作系统才会释放僵尸进程的 PCB 资源。

僵尸进程不会占用系统资源,但如果父进程长时间不处理僵尸进程,会导致系统中积累大量的僵尸进程,从而影响系统的性能和稳定性。因此,及时处理僵尸进程是操作系统管理的一个重要任务。

ps aux / ps axj  命令
    #include <stdio.h>
    #include <stdlib.h>

    int main()
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return 1;
    }
    else if(id > 0){ //parent
        printf("parent[%d] is sleeping...\n", getpid());
        sleep(30);
    }else{
        printf("child[%d] is begin Z...\n", getpid());
        sleep(5);
        exit(EXIT_SUCCESS);
    }
    return 0;
}

6.6.8 孤儿进程

孤儿进程是指其父进程已经终止或者不存在的进程。当一个进程终止时,操作系统会将其所有子进程的父进程设置为 init 进程(进程 ID 为 1 的进程,是系统启动时创建的第一个用户级进程),init 进程会负责接管孤儿进程,防止它们变成僵尸进程。

孤儿进程的特点包括:

  1. 其父进程已经终止,或者父进程已经不再运行。
  2. 它们的父进程 ID(PPID)为 1,即它们的父进程被设置为 init 进程(操作系统)。

由于孤儿进程的父进程已经不再存在,因此它们的父进程无法收集它们的退出状态。然而,由于 init 进程会定期调用 wait()waitpid() 系统调用来获取其子进程的退出状态,因此孤儿进程不会变成僵尸进程,它们的退出状态会被 init 进程处理。

总之,孤儿进程是指其父进程已经终止或不存在的进程,它们的父进程 ID 被设置为 1,由 init 进程接管。通过 init 进程的管理,孤儿进程不会成为僵尸进程。

6.7 进程优先级

6.7.1 基本概念

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

6.7.2 查看系统进程

ps 命令是一个常用的用于显示当前运行进程的命令,在 Unix 和类 Unix 系统中广泛使用。

我们很容易注意到其中的几个重要信息,有下:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

6.7.3 PRI & NI

  • PRI(优先级):PRI 是进程的实际优先级,即决定了进程在 CPU 上执行的先后顺序。PRI 的值越小,进程的优先级越高,越有可能被 CPU 优先执行。
  • NI(nice 值):NI 是用来修正进程优先级的值。它可以调整进程的实际优先级,使其在 CPU 调度时获得更高或更低的优先级。NI 的取值范围是 -20 到 +19,其中负值表示更高的优先级,正值表示更低的优先级。

在 Linux 系统中,可以使用 nice 命令为进程设置 nice 值,从而调整进程的优先级。例如,nice -n -10 ./my_program 命令将会将 my_program 进程的 nice 值设置为 -10,使其拥有更高的优先级。

PRI(new)=PRI(old)+nice

6.7.4 查看进程优先级的命令

用top命令更改已存在进程的nice:

  • top
  • 进入top后按“r”–>输入进程PID–>输入nice值

6.7.5 其他概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

6.7.6 Linux2.6内核进程调度队列

  • CPU Runqueue(运行队列): 每个 CPU 拥有一个运行队列,其中存放着当前可执行的进程。如果系统有多个 CPU,则每个 CPU 都有自己的运行队列。运行队列的作用是存放待执行的进程,以便 CPU 调度器选择合适的进程执行。
  • 优先级:
    • 普通优先级:普通进程的优先级范围是 100~139。这个范围与 nice 值的取值范围相对应。
    • 实时优先级:实时进程的优先级范围是 0~99,通常由实时调度策略处理,不关心优先级的调度。
  • 活动队列(Active Queue): 活动队列存放的是所有还未耗尽时间片的进程,按照优先级进行排列。活动队列上的进程会被 CPU 调度器选择执行。
  • 过期队列(Expired Queue): 过期队列存放的是已经耗尽时间片的进程。当活动队列上的进程都被处理完毕时,CPU 调度器会处理过期队列上的进程,并重新计算它们的时间片。
  • Active 指针和 Expired 指针: 这两个指针分别指向活动队列和过期队列。通过交换这两个指针的内容,相当于将过期队列中的进程置为活动状态,实现了一批新的活动进程的调度。
  • Bitmap(位图): 为了提高查找非空队列的效率,内核使用位图表示队列是否为空。这样,就可以在常数时间内找到非空队列,提高了查找效率。

总的来说,Linux 2.6 内核的进程调度队列采用了 O(1) 算法,即在系统中查找最合适的调度进程的时间复杂度是一个常数,不会随着进程数量的增加而增加。这种设计使得系统能够高效地处理大量的进程,保证了系统的响应性和性能。

6.7.7 进程是如何切换上下文的

当我们在函数内定义栈临时变量时,这些变量的生命周期与函数的调用栈帧相关联。当函数被调用时,为函数分配的栈空间用于存储函数的局部变量和其他临时数据。当函数执行完成后,其对应的栈空间会被释放,此时函数内定义的临时变量也随之被销毁。虽然函数的栈空间在函数执行完成后被释放,但是栈空间中的数据并不会立即消失,它们可能会留存在栈内存中一段时间。因此,如果在函数内部定义的临时变量的地址被传递给外部,外部仍然可以通过这些地址访问到栈内存中的数据,但是这种操作是不安全的,因为栈内存的数据随时可能被覆盖。

关于程序/进程如何知道当前运行到哪里,以及如何进行函数间跳转的问题,主要涉及到CPU的执行过程和操作系统的调度机制。当程序/进程被加载到内存中并执行时,CPU会根据程序计数器(PC,例如x86架构中的EIP寄存器)中存储的地址来逐条执行指令。程序计数器存储着当前正在执行的指令的地址,CPU会按照程序计数器中的地址来顺序执行指令,直至遇到函数调用、跳转等指令,或者发生中断等情况。在函数调用时,CPU会将当前的执行状态(包括程序计数器等寄存器的值)保存到栈中,然后跳转到函数的入口地址开始执行函数代码。函数执行完成后,CPU会从栈中恢复先前保存的执行状态,继续执行函数调用指令之后的指令。

至于进程的硬件上下文和CPU寄存器,每个进程在CPU中都有自己的硬件上下文,其中包括一组通用寄存器(例如x86架构中的eax、ebx等)、程序计数器、堆栈指针等。当操作系统进行进程切换时,会保存当前进程的硬件上下文,并加载下一个进程的硬件上下文,从而实现进程间的切换。这样,不同进程之间的寄存器数据是相互隔离的,每个进程有自己的一套寄存器数据。

进程的上下文数据主要保存在进程的 PCB(进程控制块)中。当进程被暂停执行时,操作系统会将当前进程的硬件上下文数据保存到其对应的 PCB 中,以便稍后重新恢复执行。因此,PCB中保存了进程的上下文信息,包括寄存器的值、程序计数器、栈指针等,可以说 PCB 存储着进程的完整状态,使得进程在切换执行时能够恢复到之前的状态。

6.8 命令行参数

6.8.1 main函数的参数

int main(int argc, char *argv[])

  • argc:表示命令行参数的数量(argument count),即程序运行时在命令行中输入的参数的个数。这个参数的值至少为1,因为第一个参数通常是程序的名称。
  • argv:是一个指向指针的指针(argument vector),它是一个字符串数组,每个元素都是一个指向一个以 null 结尾的字符串(C 字符串)的指针,表示一个命令行参数。argv[0] 通常是程序的名称,而其他元素是通过命令行传递给程序的参数。

获取命令行参数:

#include<stdio.h>
int main(int argc,char* argv[])
{
  for(int i = 0; argv[i]; ++i)
  {
    printf("argv[%d] = %s\n",i,argv[i]);
  }
  return 0;
}

其实还有第三个参数

int main(int argc, char *argv[], char *envp[])

envp 是一个字符串数组,每个元素都是一个指向以 null 结尾的字符串(C 字符串)的指针,表示一个环境变量。每个环境变量的格式通常是 “name=value”。

6.8.2 本地变量

在Linux中,本地变量通常指的是在Shell脚本或者Shell会话中定义的变量,这些变量的作用域仅限于当前的Shell环境或者当前执行的脚本内部,而不会影响到其他的Shell环境或者其他的脚本。

定义和使用本地变量:

  1. 直接赋值:
variable_name=value
  1. 使用export命令,导出环境变量,导在父进程bash(对于子Shell有效):
export variable_name=value
  1. 查看自己的变量

env |grep variable_name

如果你在Shell脚本中或者Shell会话中定义的变量没有被保存到配置文件中,它们确实会在重启后消失。通常情况下,要使变量在系统重启后依然可用,你需要将它们添加到适当的配置文件中,比如~/.bashrc~/.bash_profile/etc/profile等。

这些文件会在每次用户登录时被加载,所以将变量定义放在这些文件中可以确保它们在系统重启后仍然可用。

6.8.3 环境变量

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但 是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

比如:cd ~实际上是使用了$HOME环境变量。~是一个缩写,它代表当前用户的主目录,实际上被替换为$HOME环境变量的值。所以,无论是cd ~还是cd $HOME,都会将当前工作目录更改为当前用户的主目录。

环境变量相关命令:

  1. echo: 显示某个环境变量值。例如,echo $HOME会打印出当前用户的主目录路径。
  2. export: 设置一个新的环境变量。例如,export MY_VARIABLE=value会将MY_VARIABLE设置为value并将其导出为环境变量,使其在当前Shell会话及其子进程中可用。
  3. env: 显示所有环境变量。运行env命令会列出当前Shell会话中的所有环境变量及其值。
  4. unset: 清除环境变量。例如,unset MY_VARIABLE会从环境中删除MY_VARIABLE
  5. set: 显示本地定义的shell变量和环境变量。set命令会显示当前Shell会话中的所有本地定义的变量和环境变量,包括HOME环境变量。

**环境变量组织方式:**每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串

获取环境变量表

#include<stdio.h>
int main(int argc,char* argv[],char* envp[])
{
  for(int i = 0; envp[i]; ++i)
  {
    printf("envp[%d] = %s\n",i,envp[i]);
  }
  return 0;
}

6.8.4 PATH

PATH是一个环境变量,用于指定操作系统在执行命令时查找可执行文件的路径。在Linux和Unix系统中,当你输入一个命令时,操作系统会按照PATH环境变量中指定的顺序在不同的目录中查找这个命令的可执行文件。如果找到了,就会执行这个命令,否则会提示“command not found”。

在Linux中,PATH环境变量通常包含一系列目录,这些目录由冒号(:)分隔开。你可以通过echo $PATH命令来查看当前PATH环境变量的值。

例如,一个典型的PATH环境变量可能看起来像这样:

echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

这意味着当你输入一个命令时,系统会按照上述顺序在这些目录中查找是否存在对应的可执行文件。

要修改PATH环境变量,可以通过编辑~/.bashrc~/.bash_profile文件来实现。例如,要将新目录/my/custom/directory添加到PATH中,你可以在~/.bashrc文件中添加如下行:

export PATH=/my/custom/directory:$PATH

然后使用source ~/.bashrc命令使修改生效,或者重新登录到你的Shell会话中。这样,在你的下一个Shell会话中,/my/custom/directory就会被添加到PATH中,并且系统会在该目录中查找可执行文件。

如何获取环境变量:

  1. 命令行第三个参数,

  2. 通过第全局变量environ获取

    libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。

  3. 通过系统调用获取或设置环境变量 putenvgetenv

6.8.5 本地变量VS环境变量

本地变量只在bash进程内部有效,不会被子进程继承下去

环境变量通过让所有的子进程继承的方式,实现自身的全局性

Linux的命令分类:

  1. 常规命令,shell fork 让子进程执行
  2. 内建命令,shell 命令行的一个函数,可以直接读取shell内部定义的本地变量。

6.9 进程地址空间

6.9.1 c语言的程序地址空间

1

底端:0000 0000 顶端:FFFF FFFF (在32位系统中,地址空间是32位,因此能够表示的地址数量是2^32,即大约4GB。这个地址空间被划分为不同的区域,包括程序代码、数据、堆栈等。)

代码测试:

int un_gval;
int init_gval=100;

struct s
{
    int a;
    int b;
    int c;
};

int main(int argc, char *argv[], char *env[])
{
    printf("code addr: %p\n", main);
    char *str = "hello Linux";

    printf("read only char addr: %p\n", str);
    printf("init global value addr: %p\n", &init_gval);
    printf("uninit global value addr: %p\n", &un_gval);

    char *heap1 = (char*)malloc(100);
    char *heap2 = (char*)malloc(100);
    char *heap3 = (char*)malloc(100);
    char *heap4 = (char*)malloc(100);
    static int a = 0;
    printf("heap1 addr : %p\n", heap1);
    printf("heap2 addr : %p\n", heap2);
    printf("heap3 addr : %p\n", heap3);
    printf("heap4 addr : %p\n", heap4);

    printf("stack addr : %p\n", &str);
    printf("stack addr : %p\n", &heap1);
    printf("stack addr : %p\n", &heap2);
    printf("stack addr : %p\n", &heap3);
    printf("stack addr : %p\n", &heap4);
    printf("a addr : %p\n", &a);

    int i= 0;
    for(; argv[i]; i++)
    {
        printf("argv[%d]: %p\n",i, argv[i]);
    }

    for(i=0; env[i]; i++)
    {
        printf("env[%d]: %p\n", i, env[i]);
    }
    return 0;
}

堆的使用是整体向下增长,局部向上使用 ,最低地址是起始地址(比如数组,结构体,起始地址+偏移量访问)

堆栈相对而生

static 就是全局变量,静态区就是已初始化全局变量区

但是再来一段代码

#include <stdio.h>
#include <unistd.h>

int g_val = 100;

int main()
{
    pid_t id = fork();

    if(id < 0)
    {
        perror("fork");
        return 0;
    }
    else if(id == 0)
    {
        // 子进程
        while(1)
        {
            printf("我是子进程,pid=%d,ppid=%d,g_val=%d,&g_val=%d\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
            g_val = 200;
        }
    }
    else 
    {
        // 父进程
        while(1)
        {
            printf("我是父进程,pid=%d,ppid=%d,g_val=%d,&g_val=%d\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
        }
    }
    return 0;
}

我们发现,父子进程,输出地址是一致的,但是变量内容不一样。

所以,引出概念:

在现代操作系统中,包括Linux,用户程序所使用的地址空间是虚拟的。这意味着用户程序看到的地址(通常是指针值)实际上是虚拟地址,而不是真正的物理地址。虚拟地址是由操作系统提供给用户程序的一种抽象,它使得每个进程都认为自己拥有独立的内存空间,而不必担心其他进程的影响。

操作系统负责将用户程序所使用的虚拟地址转换为对应的物理地址。这个过程通常称为地址转换(address translation)。操作系统使用一种叫做页表(page table)的数据结构来实现地址转换。页表将虚拟地址映射到真正的物理地址上。这样,用户程序可以自由地使用虚拟地址,而无需关心物理内存的实际分布情况。

6.9.2 进程地址空间

之前说的程序地址空间是不准确的,准确的应该说成进程地址空间。

分页虚拟地址空间

2

前面的代码,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址。

写时拷贝(Copy-on-Write,COW)是一种优化技术,用于在父进程创建子进程时延迟对内存的复制,直到子进程需要修改这些内存时才进行实际的复制操作。这样可以减少内存的重复拷贝,提高系统的性能和效率。

在Linux系统中,当父进程创建子进程时,子进程会继承父进程的地址空间。如果父进程和子进程之间没有对共享内存进行任何修改,那么这些内存页将会被标记为只读,并且父进程和子进程将共享相同的物理内存页。只有当父进程或子进程试图修改这些内存页中的数据时,才会触发写时拷贝机制。

具体来说,当父进程或子进程尝试对共享内存页进行写操作时,操作系统会检查该内存页的引用计数。如果引用计数大于1,说明有多个进程共享这个内存页,此时操作系统会为子进程分配一个新的物理内存页,并将该内存页中的数据复制到新的物理内存页中。然后,操作系统将这个新的物理内存页映射到子进程的地址空间,同时更新页表,使得子进程能够访问这个新的内存页。这样一来,父进程和子进程就各自拥有了独立的内存页,它们可以自由地修改自己的内存数据,而不会影响到对方。

写时拷贝技术使得父子进程之间共享内存变得更加高效和安全,因为只有在真正需要修改内存时才会进行复制操作,减少了不必要的内存复制和内存占用。

###6.9.3 什么是地址空间?什么是区域划分?

在操作系统中,每个进程都有自己的地址空间,而操作系统负责管理这些地址空间。地址空间是操作系统内核的一个重要数据结构对象,通常由一个内核中的结构体来描述。

这个内核数据结构对象包含了进程的地址空间的各个方面,例如代码区、数据区、堆、栈等。它还包括了管理这些区域所需的数据结构,例如页表、内存映射表等。此外,地址空间的管理也涉及到内存分配、释放、共享等操作。

区域划分(Memory Region)指的是操作系统在管理进程地址空间时,将地址空间划分为不同的区域,并为每个区域分配特定的内存权限和属性。在Linux内核中,mm_struct(Memory Management Structure)结构体用于描述进程的地址空间布局和管理,它包含了进程的代码、数据、堆、栈等信息,以及管理这些区域的相关数据结构,例如页表、内存映射表等。

地址空间也要被OS管理起来,每一个进程都要有地址空间。系统中,一定要对地址空间做管理。

地址空间最终是一个内核的数据结构对象,就是一个内核的结构体。区域划分就是mm_struct。

6.9.4 为什么要有地址空间?

  1. 以统一的视角看待内存:通过地址空间和页表,操作系统可以为每个进程提供一个统一的、有序的虚拟内存空间。这使得进程可以将内存数据组织为不同的区域,例如代码区、数据区、堆、栈等,从而更方便地进行内存管理和访问。
  2. 虚拟地址空间的安全检查:虚拟地址空间可以有效地进行进程访问内存的安全检查。操作系统可以利用页表中的访问权限位来限制进程对内存的访问,例如只读、读写、执行等权限,从而提高系统的安全性。
  3. 进程管理和内存管理的解耦:通过页表,操作系统可以将进程映射到不同的物理内存,从而实现进程的独立性。CR3页表寄存器存储了当前进程的页表的物理地址,当进行进程切换时,操作系统会更新CR3寄存器的值,以切换到新的进程的地址空间。这种解耦可以使得操作系统更加灵活地管理进程和内存,并提高系统的可维护性和可扩展性。
  4. 理解切换:切换是指操作系统在多任务环境下,从一个进程转换到另一个进程的过程。在切换过程中,操作系统需要保存当前进程的上下文(例如寄存器状态、程序计数器等),然后加载下一个进程的上下文,并将控制权转移到新的进程。切换可以分为进程切换和线程切换两种,其中进程切换涉及到地址空间的切换,而线程切换只涉及到线程的上下文切换。
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
1. Linux 是一种开源的操作系统,最初由芬兰的林纳斯·托瓦兹(Linus Torvalds)创建并发布。 2. Linux 分为内核(kernel)和发行版(distribution)。内核是操作系统的核心组件,而发行版则是将内核与其他软件包整合在一起并提供给用户使用的版本。 3. 常见的 Linux 发行版有 Ubuntu、Debian、Fedora、CentOS、SUSE 等。 4. Linux 的文件系统采用层次式结构,以根目录(/)作为起始点,其下有各种目录,例如 /bin(存放可执行二进制文件)、/etc(存放配置文件)、/home(存放用户文件夹)等。 5. Linux 支持多用户和多任务的操作。每个用户都有自己的用户名和密码,并且可以在同一时间进行多个任务。 6. Linux 使用 shell 来与用户进行交互,常见的 shell 有 Bash、Zsh 等。通过 shell,用户可以执行命令、管理文件和目录、配置系统等。 7. Linux 支持各种网络协议,可以作为服务器来提供各种服务,例如 Web 服务器(如 Apache)、邮件服务器(如 Postfix)、数据库服务器(如 MySQL)等。 8. Linux 提供了丰富的命令行工具和脚本语言,可以进行自动化任务、批量处理等操作。 9. Linux 提供了强大的安全性和权限控制机制,可以对文件、目录和用户进行权限管理,以保护系统的安全。 10. Linux 社区庞大活跃,有大量的开源软件和工具可供使用和学习,并且有丰富的文档和在线资源可供参考。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小豪GO!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值