一、系统知识 1. profile、bashrc、bash_profile之间的关系 /etc/profile: 此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行. 并从/etc/profile.d目录的配置文件中搜集shell的设置。/etc/bashrc: 为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取。~/.bash_profile: 每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件。~/.bashrc: 该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该该文件被读取。~/.bash_logout: 当每次退出系统(退出bash shell)时,执行该文件. 另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc /profile中的变量,他们是”父子”关系。~/.bash_profile是交互式、login 方式进入 bash 运行的~/.bashrc 是交互式 non-login 方式进入 bash 运行的通常二者设置大致相同,所以通常前者会调用后者。 2. service的几个知识 2.1. service linux服务管理 service有两种管理方式,一个是用service命令,一个使用systemctl,自己感觉没啥区别,都需要写一个service文件 service文件位置: /usr/lib/systemd/system/ 服务文件内容一般如下,具体作用查看man systemd.service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [Unit] Description =OpenBSD Secure Shell serverDocumentation =man:sshd(8 ) man:sshd_config(5 )After =network.target auditd.serviceConditionPathExists =!/etc/ssh/sshd_not_to_be_run[Service] EnvironmentFile =-/etc/default/sshExecStartPre =/usr/sbin/sshd -tExecStart =/usr/sbin/sshd -D $SSHD_OPTS ExecReload =/usr/sbin/sshd -tExecReload =/bin/kill -HUP $MAINPID KillMode =processRestart =on -failureRestartPreventExitStatus =255 Type =notifyRuntimeDirectory =sshdRuntimeDirectoryMode =0755 [Install] WantedBy =multi-user.targetAlias =sshd.service
配置了WantedBy后,使用systemctl enable xxx可以设置服务在某个模式下启动,会创建一个连接到/etc/systemd/system/xxx.target.wanted/ 一般配置开机启动设置WantedBy为multi-user.target,然后执行上一行的命令即可 Type=forking会检测pidfile的进程状态2.2. 查看服务启动日志 1 2 # -f 跟踪日志 journalctl -u [service_name] -f
3. 开机启动脚本 update-rc.d enable/disable/defaults
4. 设备event事件查看 1 udevadm monitor --environment --udev
5. 用户组 5.1. 几个基本用法 1 2 3 4 5 6 # 将用户变更主组到group_name usermod -g [group_name] [user_name] # 添加用户组属性,不修改原有所属组 usermod -a -G [group_name] [user_name] # 查看用户所属组 groups [user_name]
6. 查看系统启动时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # # 查看最后一次系统启动时间 => who -b 系统引导 2022-01-06 10:22 # 查看当前系统运行时间 => who -r 运行级别 3 2022-01-06 10:24 # => last reboot reboot system boot 4.19.181 Thu Jan 6 10:22 still running reboot system boot 4.19.181 Sat Dec 18 20:06 - 10:20 (18+14:14) reboot system boot 4.19.181 Sat Dec 18 19:26 - 20:04 (00:38) reboot system boot 4.19.181 Tue Dec 7 13:49 - 20:04 (11+06:15) reboot system boot 4.19.163 Mon Nov 22 22:41 - 13:47 (14+15:06) wtmp begins Mon Nov 22 10:40:48 2021 # # 只能看当前时间和启动了多久 => uptime 10:37:02 up 3 days, 17:50, 0 users, load average: 5.29, 5.16, 5.04 # # 展示从系统启动到现在经过多少s => cat /proc/uptime 16235.69 22108.34
7. 定时任务 7.1. cron格式说明 1 2 3 4 5 6 7 8 9 * * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ 星期 (0 - 7) (0或7都是星期日) │ │ │ │ └───── 月份 (1 - 12) │ │ │ └────────── 日期 (1 - 31) │ │ └─────────────── 小时 (0 - 23) │ └──────────────────── 分钟 (0 - 59) └───────────────────────── 秒 (0 - 59, optional)
8. 进程的几种状态 top进程状态解析
9. /dev 介绍 9.1. /dev/random和/dev/urandom 用于产生真随机数,其中/dev/random依赖系统中断,所以较慢,但是随机性更好 /dev/urandom不依赖系统中断,随机性较差它们都依赖一个系统熵熵足够,两个都是真随机 熵不足,/dev/random会阻塞,/dev/urandom不会阻塞,依赖算法 查看熵池大小: /proc/sys/kernel/random/poolsize 查看熵池可用的数量: /proc/sys/kernel/random/entropy_avail /dev/random和/dev/urandom都是字符型设备,可以当作文件直接读取1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 => stat /dev/random File: /dev/random Size: 0 Blocks: 0 IO Block: 4096 character special file Device: 0,5 Inode: 8 Links: 1 Device type: 1,8 Access: (0666/crw-rw-rw-) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2022-03-22 09:53:37.442304649 +0800 Modify: 2022-03-22 09:53:37.442304649 +0800 Change: 2022-03-22 09:53:37.442304649 +0800 Birth: - => stat /dev/urandom File: /dev/urandom Size: 0 Blocks: 0 IO Block: 4096 character special file Device: 0,5 Inode: 9 Links: 1 Device type: 1,9 Access: (0666/crw-rw-rw-) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2022-03-22 09:53:37.442304649 +0800 Modify: 2022-03-22 09:53:37.442304649 +0800 Change: 2022-03-22 09:53:37.442304649 +0800 Birth: -
如果不存在,可以通过下面命令挂载
1 2 mknod /dev/random c 1 8 mknod /dev/urandom c 1 9
9.2. /dev/kmsg 对接dmesg日志 1 2 3 4 5 6 7 8 9 => stat /dev/kmsg File: /dev/kmsg Size: 0 Blocks: 0 IO Block: 4096 character special file Device: 0,6 Inode: 10 Links: 1 Device type: 1,11 Access: (0644/crw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2024-09-27 10:33:35.640000000 +0800 Modify: 2024-09-27 10:33:35.640000000 +0800 Change: 2024-09-27 10:33:35.640000000 +0800 Birth: 2024-09-27 10:33:29.151054145 +0800
想要输出到dmesg,可以直接对齐进行输出,如echo "aaa" > /dev/kmsg,这样dmesg里面可以直接看到对应日志
10. /proc介绍 10.2. /proc/[pid] 进程信息 /proc/self为当前进程信息,可以直接在程序中进行读取1) fd 保存当前进程打开的句柄 2) maps 进程内存空间信息 3) limits 显示进程的一些限制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 => cat /proc/self/limits Limit Soft Limit Hard Limit Units Max cpu time unlimited unlimited seconds Max file size unlimited unlimited bytes Max data size unlimited unlimited bytes Max stack size 8388608 unlimited bytes Max core file size unlimited unlimited bytes Max resident set unlimited unlimited bytes Max processes 14993 14993 processes Max open files 1024 4096 files Max locked memory 16777216 16777216 bytes Max address space unlimited unlimited bytes Max file locks unlimited unlimited locks Max pending signals 14993 14993 signals Max msgqueue size 819200 819200 bytes Max nice priority 0 0 Max realtime priority 0 0 Max realtime timeout unlimited unlimited us
硬限制和软限制
硬限制只能由root用户进行扩大 软限制由用户自己修改,不能超过硬限制 4) cwd 当前工作目录 cwd是一个软连接,指向对应的工作目录 查看进程当前工作目录 1 2 => ll /proc/57733/cwd lrwxrwxrwx 1 test test 0 Aug 16 09:29 /proc/57733/cwd -> /home/test
10.3. /proc/sys 保存内核的参数 1) 列举几个内核参数含义 /proc/sys/fs/file-max: 系统最多能打开的文件句柄数/proc/sys/kernel/threads-max: 系统最大线程数量/proc/sys/kernel/pid-max: pid的最大数2) /proc/sys/net/ipv4/conf/interface/rp_filter rp_filter在内核代码如何生效
对应sysctl变量net.ipv4.conf.[interface].rp_filter 内核取值会取all和当前进入的网卡较大的那个值 rp_filter设置成1则会在当前网卡收包处理时,如果发现源地址作为目的地址路由不是指向此网卡,将会丢包,具体原理看上面链接设置值的说明见 rp_filter|sysctl-explorer.net 10.4. /proc/interrupts cpu累积收到的硬中断次数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 => sudo cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 0: 20 0 0 0 0 0 0 0 IR-IO-APIC 2-edge timer 1: 0 0 0 0 0 0 9998 0 IR-IO-APIC 1-edge i8042 8: 0 0 0 0 0 0 0 0 IR-IO-APIC 8-edge rtc0 9: 0 8 0 0 0 0 0 0 IR-IO-APIC 9-fasteoi acpi 12: 0 0 0 0 0 140 0 0 IR-IO-APIC 12-edge i8042 14: 0 55570 0 0 0 0 0 0 IR-IO-APIC 14-fasteoi INT344B:00 16: 0 0 0 0 759315 0 0 0 IR-IO-APIC 16-fasteoi idma64.0, i2c_designware.0, i801_smbus 17: 0 0 0 0 0 0 0 0 IR-IO-APIC 17-fasteoi idma64.1, i2c_designware.1 120: 0 0 0 0 0 0 0 0 DMAR-MSI 0-edge dmar0 121: 0 0 0 0 0 0 0 0 DMAR-MSI 1-edge dmar1 122: 0 0 0 0 0 0 0 0 IR-PCI-MSI 458752-edge PCIe PME, aerdrv 123: 0 0 0 0 0 0 0 0 IR-PCI-MSI 473088-edge PCIe PME, aerdrv 124: 0 0 0 0 0 0 0 0 IR-PCI-MSI 475136-edge PCIe PME, aerdrv 125: 0 0 5073 0 32 0 0 0 IR-PCI-MSI 327680-edge xhci_hcd 126: 0 0 0 17 0 0 0 0 IR-PCI-MSI 1572864-edge nvme0q0 127: 0 0 0 0 19640 0 0 0 IR-PCI-MSI 1572865-edge nvme0q1 128: 0 10107 0 0 0 0 0 0 IR-PCI-MSI 1572866-edge nvme0q2 129: 0 0 13191 0 0 0 0 0 IR-PCI-MSI 1572867-edge nvme0q3 130: 0 0 0 7165 0 0 0 0 IR-PCI-MSI 1572868-edge nvme0q4 131: 0 0 0 0 0 9568 0 0 IR-PCI-MSI 1572869-edge nvme0q5 132: 0 0 0 0 0 0 10363 0 IR-PCI-MSI 1572870-edge nvme0q6 133: 0 0 0 0 0 0 0 6400 IR-PCI-MSI 1572871-edge nvme0q7 134: 0 55570 0 0 0 0 0 0 INT344B:00 99 ETD2303:00 135: 0 0 0 0 0 0 41 0 IR-PCI-MSI 360448-edge mei_me 136: 0 0 0 0 0 0 0 357 IR-PCI-MSI 1048576-edge iwlwifi 137: 0 12 0 0 0 0 43 0 IR-PCI-MSI 524288-edge nvkm 138: 0 0 103 630743 0 4805 0 0 IR-PCI-MSI 32768-edge i915 139: 0 0 0 1144 0 0 0 0 IR-PCI-MSI 514048-edge snd_hda_intel:card0 NMI: 23 23 22 22 23 22 22 24 Non-maskable interrupts LOC: 1949275 1734042 1936075 1538172 1801814 1862963 1854126 2040015 Local timer interrupts SPU: 0 0 0 0 0 0 0 0 Spurious interrupts PMI: 23 23 22 22 23 22 22 24 Performance monitoring interrupts IWI: 12652 12187 12147 310841 13382 16848 12683 11054 IRQ work interrupts RTR: 0 0 0 0 0 0 0 0 APIC ICR read retries RES: 6294 10746 6760 6099 6037 6368 5831 7977 Rescheduling interrupts CAL: 142829 133636 126459 119454 124950 121592 125146 125214 Function call interrupts TLB: 81943 78817 75641 75416 76803 74568 81688 81265 TLB shootdowns TRM: 0 0 0 0 0 0 0 0 Thermal event interrupts THR: 0 0 0 0 0 0 0 0 Threshold APIC interrupts DFR: 0 0 0 0 0 0 0 0 Deferred Error APIC interrupts MCE: 0 0 0 0 0 0 0 0 Machine check exceptions MCP: 6 7 7 7 7 7 7 7 Machine check polls ERR: 0 MIS: 0 PIN: 0 0 0 0 0 0 0 0 Posted-interrupt notification event NPI: 0 0 0 0 0 0 0 0 Nested posted-interrupt event PIW: 0 0 0 0 0 0 0 0 Posted-interrupt wakeup event
说明
第一列是中断号,这个后面单独一个表格说明 后面几列就是这个中断号在每个cpu的累积次数了 最后一列是中断控制器和关联的设备 1) 中断号说明 TODO 待补充
10.5. /proc/softirqs cpu累积收到的软中断次数 1 2 3 4 5 6 7 8 9 10 11 12 => cat /proc/softirqs CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 HI: 85496 91206 68246 774236 99925 84401 78588 64873 TIMER: 81679 56531 55439 194830 59389 55766 55408 55580 NET_TX: 2 0 0 1 2 1 1 0 NET_RX: 4112 3831 4385 3805 3351 4641 4935 4283 BLOCK: 115 116 82 167 158 273 70 162 IRQ_POLL: 0 0 0 0 0 0 0 0 TASKLET: 127 0 4949 1 25 2 11653 31 SCHED: 588778 555570 531161 558175 524359 507878 504310 510537 HRTIMER: 0 0 0 0 0 0 0 0 RCU: 324282 324077 318224 355089 325243 321701 319715 320503
TODO 说明所有的软中断是干嘛的
11. 管道 进程间通信可以使用管道 管道的本质是起了两个子进程,一个进程的标准输出为另一个进程的标准输入 11.1. 示例 1 2 => { echo "aaa"; read n } | { cat; read p } aaa
1 2 3 4 5 6 => ps -auxf ... test_user 2262 0.0 0.0 16840 8192 pts/2 Ss 11:41 0:01 | \_ -zsh test_user 10342 0.0 0.0 16840 3412 pts/2 S+ 14:20 0:00 | | \_ -zsh test_user 10343 0.0 0.0 10792 976 pts/2 S+ 14:20 0:00 | | \_ cat ...
1 2 3 4 5 6 7 8 9 10 11 => ls -l /proc/10342/fd total 0 lrwx------ 1 test_user test_user 64 Jun 20 14:27 0 -> /dev/pts/2 l-wx------ 1 test_user test_user 64 Jun 20 14:27 1 -> 'pipe:[224646]' lrwx------ 1 test_user test_user 64 Jun 20 14:27 10 -> /dev/pts/2 lrwx------ 1 test_user test_user 64 Jun 20 14:27 2 -> /dev/pts/2 => ls -l /proc/10343/fd total 0 lr-x------ 1 test_user test_user 64 Jun 20 14:27 0 -> 'pipe:[224646]' lrwx------ 1 test_user test_user 64 Jun 20 14:27 1 -> /dev/pts/2 lrwx------ 1 test_user test_user 64 Jun 20 14:27 2 -> /dev/pts/2
左边进程的标准输出到管道,右边进程的标准输入为管道 12. 文件类型 linux下存在很多种文件类型,一切皆文件 使用ls -l可以查看文件类型 几种类型
-: 普通文件,文本或二进制d: 目录l: 链接b: 块设备,磁盘就是块设备,可以向前向后读c: 字符型设备,比如/dev/zero、/dev/random等,只能读取当前s: 套接字文件,用于链接建立等p: 管道文件,不过一般是命名管道,正常的管道一般不可见13. 进程 13.1. 最多能创建多少线程 取决于进程的虚拟内存大小和每个线程需要占用的栈大小 32位系统下,4G虚拟内存,内核占用1G,剩余3G。每个线程栈大小,由ulimit -s可以查看,假设8M。那么最多创建300多个线程就会内存不足 64位系统下,取决于linux系统下的线程最大数量、全局pid号数量限制、进程可以拥有的VMA数量 13.2. 最大能打开的文件句柄数 14. 可执行文件的结构 14.1. 查看可执行文件的大小 1 2 3 4 5 #include <stdio.h> int main (int argc, char **argv) { printf ("Hello, world" ); }
1 2 3 4 => gcc -g main.cpp => size a.out text data bss dec hex filename 1576 616 8 2200 898 a.out
14.2. 解析 1) bss 未初始化数据区 Block Started by Symbol 以符号开始的段,通常指未初始化的数据段 代码中定义的未初始化的全局变量和静态变量存放到此区域 此区域会被内核初始化成0,所以为什么没有初始化的全局变量和静态变量会是0的原因 1 2 3 4 5 6 7 #include <stdio.h> int a[1000 ];int main (int argc, char **argv) { printf ("Hello, world" ); }
1 2 3 4 => gcc -g main.cpp => size a.out text data bss dec hex filename 1576 616 4008 6200 1838 a.out
2) data 静态数据和全局初始化的数据区 初始化的静态变量和全局变量会放到此区域 初始化成0的变量会被编译器优化放到bss段 1 2 3 4 5 6 7 8 #include <stdio.h> int a = 100 ;int b = 0 ;int main (int argc, char **argv) { printf ("Hello, world" ); }
1 2 3 4 => gcc -g main.cpp => size a.out text data bss dec hex filename 1576 620 12 2208 8a0 a.out
可以看到定义了两个int型变量,一个增加了data段,一个增加了bss段,各4个字节 3) text 代码段,存放代码指令 1 2 3 4 5 6 #include <stdio.h> int main (int argc, char **argv) { printf ("Hello, world" ); printf ("Hello, world1" ); }
1 2 3 4 => gcc -g main.cpp => size a.out text data bss dec hex filename 1605 616 8 2229 8b5 a.out
bss和data都没增加,text段增加了代码指令 二、有用的几个技巧 1. linux启动到文本界面和到图形界面 1 2 3 4 # 启动到文本界面 systemctl set-default multi-user.target # 启动到图形界面 systemctl set-default graphical.target
2. 修改硬盘的uuid 1 2 3 4 # 随机生成uuid写入 uuidgen | xargs tune2fs /dev/sda5 -U # 写入自己生成uuid tune2fs -U c1b9d5a2-f162-11cf-9ece-0020afc76f16 /dev/sdb1
3. hostname立即生效 1 hostname "$(cat /etc/hostname)"
4. bpf 主要用于网络数据分析 原理参考 Linux超能力BPF技术介绍及学习分享
5. 设置无操作用户自动退出 1 2 export TMOUT=15 # 无操作15秒自动退出 readonly TMOUT # 不允许用户修改此变量
6. 系统时间和硬件时间同步 1 2 3 4 5 6 7 # 显示硬件时间,启动就会读取的时间,和系统不一致就是每次重启时间都不对 => sudo hwclock --show 2022-12-09 17:35:28.288827+08:00 # 硬件时间同步到系统时间,开机会自动执行 => sudo hwclock --hctosys # 系统时间同步到硬件时间,可以在同步系统时间后进行执行,保证重启后和当前一致 => sudo hwclock --systohc
7. 设置时区 1 ln -sf /usr/share/zoneinfo/Asia/Beijing /etc/localtime
8. 添加一个用户 1 2 3 4 5 6 7 8 9 # 添加用户 # -d 设置home目录 # -s 设置shell useradd test -d /home/test -s /bin/zsh # 设置密码 passwd test # 添加目录 mkdir /home/test chown test:test /home/test
8.1. 给用户执行sudo的权力 需要安装sudo包,然后执行visudo 在打开的sudoers文件中,找到以下行:root ALL=(ALL) ALL。 在该行下面添加以下内容:username ALL=(ALL) ALL,其中username是你要授予管理员权限的用户名。 保存并关闭sudoers文件。 9. journalctl 检查服务日志 9.1. 选项 10. 命令行配置socks5代理 1 2 export http_proxy="socks5://127.0.0.1:1080" export https_proxy="socks5://127.0.0.1:1080"
11. 串口转USB读取 CH340系列需要看看设备有没有驱动,没有需要下载
1 2 3 => modinfo ch341 filename: /lib/modules/6.11.6-arch1-1/kernel/drivers/usb/serial/ch341.ko.zst ...
下载地址在 https://www.wch.cn/download/CH341SER_LINUX_ZIP.html
驱动没问题后,可以从dmesg中看到usb插上后映射到/dev/ttyUSBx,就可以使用minicom来进行串口读写了,打开命令如下
1 2 3 4 5 # -D /dev/ttyUSB0 打开设备是/dev/ttyUSB0 # -b 115200 设置波特率为115200 # -w 启动自动换行 # -h 使用hex显示 minicom -D /dev/ttyUSB0 -b 115200 -w
minicom快捷键
Ctrl+a,x: 退出Ctrl+a,z: 打开帮助面板Ctrl+a,c: 清屏Ctrl+a,w: 启禁用自动换行Ctrl+a,e: 启禁用输入显示三、c/c++编程 1. 获取系统信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct sysinfo { __kernel_long_t uptime; __kernel_ulong_t loads[3 ]; __kernel_ulong_t totalram; __kernel_ulong_t freeram; __kernel_ulong_t sharedram; __kernel_ulong_t bufferram; __kernel_ulong_t totalswap; __kernel_ulong_t freeswap; __u16 procs; __u16 pad; __kernel_ulong_t totalhigh; __kernel_ulong_t freehigh; __u32 mem_unit; char _f[20 -2 *sizeof (__kernel_ulong_t )-sizeof (__u32)]; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <sys/sysinfo.h> #include <errno.h> #include <string.h> #include <stdio.h> int main () { struct sysinfo info; if (sysinfo (&info)) { printf ("failed to get sysinfo, errno: %u, reason: %s\n" , errno, strerror (errno)); return 1 ; } return 0 ; }
2. 获取进程id 1 2 __pid_t getpid (void ) __THROW ;
3. 获取系统时间 一般获取系统时间有两个函数gettimeofday和clock_gettime 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 extern int clock_gettime (clockid_t __clock_id, struct timespec *__tp) __THROW __nonnull ((2 )) ;#ifndef __USE_TIME_BITS64 extern int gettimeofday (struct timeval *__restrict __tv, void *__restrict __tz) __THROW __nonnull ((1 )) ;#else # ifdef __REDIRECT_NTH extern int __REDIRECT_NTH (gettimeofday, (struct timeval *__restrict __tv, void *__restrict __tz), __gettimeofday64) __nonnull ((1 )); # else # define gettimeofday __gettimeofday64 # endif #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # define CLOCK_REALTIME 0 # define CLOCK_MONOTONIC 1 # define CLOCK_PROCESS_CPUTIME_ID 2 # define CLOCK_THREAD_CPUTIME_ID 3 # define CLOCK_MONOTONIC_RAW 4 # define CLOCK_REALTIME_COARSE 5 # define CLOCK_MONOTONIC_COARSE 6 # define CLOCK_BOOTTIME 7 # define CLOCK_REALTIME_ALARM 8 # define CLOCK_BOOTTIME_ALARM 9 # define CLOCK_TAI 11
4. errno errno线程唯一,不用担心多线程问题 errno在头文件unistd.h strerror()在头文件string.h四、内存管理 1. 虚拟内存 虚拟内存是每个进程有自己的虚拟地址空间,不同的进程之间的虚拟地址空间一样,但是对应的物理地址空间不一样 32位下,每个进程拥有4G虚拟地址空间,其中1G为内核空间,3G为用户空间 每个进程有一个页表,存放在进程的信息中,mmu处理每个进程的虚拟地址到物理地址映射时要读取不同进程的页表,通过页表寄存器指定页表地址 2. linux内存分布 64位下理论可以操作的内存范围0x0000000000000000~`0xffffffffffffffff`(16777216TB),但是linux上仅使用低47位,高17位用于扩展。内存范围如下分配
取自 https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt
Start addr End addr Size VM area description 0000000000000000 00007fffffffffff 128 TB 用户地址空间,每个进程mm指向的地址不一样 0000800000000000 ffff7fffffffffff ~16M TB 大空洞 内核地址空间: ffff800000000000 ffff87ffffffffff 8 TB … guard hole, also reserved for hypervisor ffff880000000000 ffff887fffffffff 0.5 TB LDT(Local Descriptor Table):局部描述符表 KPTI(Kernel page-table isolation):内核页表隔离 ffff888000000000 ffffc87fffffffff 64 TB 线性映射的区域 (page_offset_base) ffffc88000000000 ffffc8ffffffffff 0.5 TB … unused hole ffffc90000000000 ffffe8ffffffffff 32 TB vmalloc和ioremap空间 (vmalloc_base) ffffe90000000000 ffffe9ffffffffff 1 TB … unused hole ffffea0000000000 ffffeaffffffffff 1 TB page结构存储的位置 (vmemmap_base) ffffeb0000000000 ffffebffffffffff 1 TB … unused hole ffffec0000000000 fffffbffffffffff 16 TB KASAN影子内存 从这里开始,与56-bit布局相同: fffffc0000000000 fffffdffffffffff 2 TB … unused hole vaddr_end for KASLR fffffe0000000000 fffffe7fffffffff 0.5 TB cpu_entry_area mapping fffffe8000000000 fffffeffffffffff 0.5 TB … unused hole ffffff0000000000 ffffff7fffffffff 0.5 TB %esp fixup stacks ffffff8000000000 ffffffeeffffffff 444 GB … unused hole ffffffef00000000 fffffffeffffffff 64 GB EFI region mapping space ffffffff00000000 ffffffff7fffffff 2 GB … unused hole ffffffff80000000 ffffffff9fffffff 512 MB 内核代码区域,mapped to physical address 0 ffffffffa0000000 fffffffffeffffff 1520 MB 模块加载区域 FIXADDR_START ffffffffff5fffff ~0.5 MB kernel-internal fixmap range, variable size and offset ffffffffff600000 ffffffffff600fff 4 kB legacy vsyscall ABI ffffffffffe00000 ffffffffffffffff 2 MB … unused hole
五、进程 1. 进程间通信 进程间通信有管道、命名管道、信号、信号量、消息队列、共享内存、套接字 1.1. 管道 1) 特点 数据只能向一个方向流动 只能用于父子进程或兄弟进程 fifo的读出和写入 缓冲区有限,本身存在于内存中,分配了一个page大小 2) 创建 管道的创建需要在父进程,然后再fork出子进程使用,或给两个兄弟子进程使用 1.2. 命名管道 1) 特点 需要提供一个文件路径,两个进程通过路径进行联系,所以可以没有亲缘关系的进程通信 内部没有fifo,读写会阻塞 其他和管道一致 2) 创建 shell通过mkfifo xxx创建管道,使用>和<进行读取写入 通过代码创建,正常按照文件一样打开和读写即可 1 int mkfifo (const char *pathname, mode_t mode) ;
1.3. 信号 1) 特点 进程不知道信号什么时候来,类似于中断 信号通俗讲就是系统定义的信号,也就是kill可以发出的信号 2) 使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static int BR_SignalRegister (int sig) { struct sigaction act; memset (&act, 0 , sizeof (act)); act.sa_handler = (__sighandler_t )BR_SignalHandler; act.sa_flags = 0 ; (void )sigemptyset (&act.sa_mask); while (sigaction (sig, &act, NULL ) < 0 ) { if (errno != EINTR) { (void )fprintf (stderr, "[BugReport]sigaction %s failed: %s\n" , BR_signals[sig], strerror (errno)); return -1 ; } } return 0 ; }
1.4. 信号量 1) 特点 2) 使用 1 int semget (key_t key, int num_sems, int sem_flags)
1 2 3 4 int semop (int sem_id, struct sembuf *sem_opa, size_t num_sem_ops) ;int semctl (int sem_id, int sem_num, int command, ...) ;
1.5. 消息队列 1) 特点 就是在命名管道上加了个fifo,避免阻塞 可以通过消息类型有选择的接收数据 2) 使用 1 2 3 4 5 6 int msgget (key_t key, int msgflg) ;int msgsnd (int msqid, void *msgp, int msgsz, int msgflg) ;int msgrcv (int msgid, void *msgp, int msgsz, long msgtyp, int msgflg) ;
1.6. 共享内存 1 int shmget (key_t key, size_t size, int shmflg) ;
1.7. 套接字 六、nss: Name Service Switch Name Service Switch(NSS)是一个在Linux和其他类Unix操作系统中使用的系统组件,它提供了一种机制来管理系统中各种名称服务的配置。名称服务包括用户帐户、组、主机名、网络协议、服务和其他系统信息。NSS允许系统管理员配置系统以使用不同的名称服务,例如本地文件、NIS、LDAP等。NSS还提供了一种机制来缓存名称服务数据,以提高系统性能。在Linux系统中,NSS通常由glibc库实现。
1. 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 => cat /etc/nsswitch.conf # Name Service Switch configuration file. # See nsswitch.conf(5) for details. passwd: files systemd group: files [SUCCESS=merge] systemd shadow: files systemd gshadow: files systemd publickey: files hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns networks: files protocols: files services: files ethers: files rpc: files netgroup: files
上面配置的files等对应一个个插件,对应命名libnss_xxx.so 修改了之后,从glibc 2.33后,会自动重新加载此文件,之前的版本只在进程起来的时候加载一次 2. 插件位置 1 2 3 4 => ls /usr/lib/libnss_* /usr/lib/libnss_compat.so /usr/lib/libnss_db.so.2 /usr/lib/libnss_hesiod.so /usr/lib/libnss_libvirt.so.2 /usr/lib/libnss_resolve.so.2 /usr/lib/libnss_wins.so.2 /usr/lib/libnss_compat.so.2 /usr/lib/libnss_dns.so.2 /usr/lib/libnss_hesiod.so.2 /usr/lib/libnss_myhostname.so.2 /usr/lib/libnss_systemd.so.2 /usr/lib/libnss_db.so /usr/lib/libnss_files.so.2 /usr/lib/libnss_libvirt_guest.so.2 /usr/lib/libnss_mymachines.so.2 /usr/lib/libnss_winbind.so.2
3. 自定义插件 可以自定义so库,满足此命名,然后放到系统库路径后,添加到配置文件中 需要满足nss对应的库的符号要求 3.1. 自定义dns解析库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <stddef.h> #include <nss.h> #include <netdb.h> #include <arpa/inet.h> #include <syslog.h> #include <zconf.h> #include <stdio.h> enum nss_status _nss_hs_gethostbyname4_r( const char *name, struct gaih_addrtuple **pat, char *buffer, size_t buflen, int *errnop, int *h_errnop, int32_t *ttlp) { fprintf (stdout, "_nss_hs_gethostbyname4_r ns hook domain:%s pid:%d\n" , name, getpid ()); return NSS_STATUS_NOTFOUND; } enum nss_status _nss_hs_gethostbyname3_r( const char *name, int af, struct hostent *host, char *buffer, size_t buflen, int *errnop, int *h_errnop, int32_t *ttlp, char **canonp) { fprintf (stdout, "_nss_hs_gethostbyname3_r ns hook domain:%s pid:%d\n" , name, getpid ()); return NSS_STATUS_NOTFOUND; } enum nss_status _nss_hs_gethostbyname2_r( const char *name, int af, struct hostent *host, char *buffer, size_t buflen, int *errnop, int *h_errnop) { return _nss_hs_gethostbyname3_r( name, af, host, buffer, buflen, errnop, h_errnop, NULL , NULL ); } enum nss_status _nss_hs_gethostbyname_r( const char *name, struct hostent *host, char *buffer, size_t buflen, int *errnop, int *h_errnop) { return _nss_hs_gethostbyname3_r( name, AF_UNSPEC, host, buffer, buflen, errnop, h_errnop, NULL , NULL ); } void _nss_hs_init(void (*cb)(size_t , struct traced_file*)) { fprintf (stdout, "_nss_hs_init..." ); }
1 gcc -shared -fPIC -o libnss_test.so.2 -Wl,-soname,libnss_test.so.2 test_nss.c
放到/usr/lib下后,重启要使用gethostbyname的进程就好了 4. hosts dns解析服务 mymachines: 由systemd提供,应该是处理容器类型的dns解析resolve: 由systemd提供,是systemd-resolved.service的处理服务files: 由glibc提供,处理/etc/hostsmyhostname: 由systemd提供,处理localhost、_gateway、_outbound等域名使用,主要是查本地网卡的一些信息dns: 由glibc提供,处理dns的网络请求使用七、二进制文件格式 参考 ELF文件格式
1. 链接视图和执行视图 链接视图是以节(section)为单位,用于在链接时使用,可以是静态库、编译过程文件、动态库等 执行视图是以段(segment)为单位,是在执行时用到的视图。 目标文件.o里的代码段.text 是section(汇编中.text同理),当多个可重定向文件最终要整合成一个可执行的文件的时候(链接过程),链接器把目标文件中相同的 section 整合成一个segment,在程序运行的时候,方便加载器的加载 2. elf文件 八、dbus机制 https://blog.csdn.net/alibutter/article/details/141575257
九、cgroup 1. systemd-cgtop 查看系统当前cgroup情况 和top差不多都是实时刷新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CGroup Tasks %CPU Memory Input/s Output/s / 3134 36.8 16.4G - - user.slice 2705 27.9 27.1G - - user.slice/user-1000.slice 2705 27.9 27G - - user.slice/user-1000.slice/user@1000.service 2608 27.7 26.4G - - system.slice 199 1.9 1.3G - - system.slice/sddm.service 8 1.5 468.3M - - system.slice/NetworkManager.service 4 0.2 17.4M - - user.slice/user-1000.slice/session-5.scope 63 0.1 494.4M - - system.slice/containerd.service 68 0.1 102.1M - - system.slice/home-wangyubo-vmware.mount 1 0.0 3.7M - - user.slice/user-1000.slice/session-11.scope 22 0.0 10.5M - - system.slice/dbus-broker.service 2 0.0 6.3M - - system.slice/docker-383ac0c85f0a78abd68509f4aa44e3e83be335e91386be3074aa43190a14efd0.scope 2 0.0 12.6M - - dev-hugepages.mount - - 164K - - dev-mqueue.mount - - 36K - - init.scope 1 - 6.5M - -
1.1. 快捷键 p: 按照CGroup路径排序t: 按照task数量排序c: 按照cpu排序m: 按照内存排序i: 按照input排序+/-: 增加减少刷新间隔%: cpu显示在百分比和总时长之间切换十、linux启动引导过程 1. UEFI引导 1) 列举引导分区 PC启动会优先寻找所有硬盘的所有分区是否有esp标记,其实就是下面的EFI System,用分区工具也能看到boot,esp标记
1 2 3 4 5 6 7 8 9 10 11 12 13 14 => sudo fdisk -l /dev/nvme0n1 Disk /dev/nvme0n1: 119.24 GiB, 128035676160 bytes, 250069680 sectors Disk model: WDC PC SN520 SDAPNUW-128G-1006 Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: 26665042-C9F5-3C4E-BEAA-7215C03C71CF Device Start End Sectors Size Type /dev/nvme0n1p1 2048 2099199 2097152 1G EFI System /dev/nvme0n1p2 2099200 27265023 25165824 12G Linux filesystem /dev/nvme0n1p3 27265024 153094143 125829120 60G Linux filesystem /dev/nvme0n1p5 153094144 250069646 96975503 46.2G Linux filesystem
找到这个标记的分区后,查找里面的EFI/xxx/grubx64.efi,显示到bootloader的引导菜单,如某一个磁盘A如下
1 2 3 4 5 6 => find /boot/efi/EFI -name "*.efi" /boot/efi/EFI/GRUB/grubx64.efi /boot/efi/EFI/Microsoft/Boot/memtest.efi /boot/efi/EFI/Microsoft/Boot/bootmgfw.efi /boot/efi/EFI/Microsoft/Boot/bootmgr.efi /boot/efi/EFI/Boot/bootx64.efi
会在PC的默认引导菜单(F12或其他按键进入)显示一行(多级目录不会找,所以microsoft不会显示;Boot是兼容兜底用的的也不会显示),B磁盘同样操作追加到后面。
2) 选中引导分区后,加载分区里面的EFI/xxx/grubx64.efi 加载这个二进制后,基于二进制里面的代码查找grub.cfg。二进制里面会设置prefix,也就是grub.cfg所在的目录。可以用strings命令查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # archlinux上的grub-install输出如下,默认找硬盘分区3(基于配置生成)的/boot/grub => strings /boot/efi/EFI/GRUB/grubx64.efi | grep -i boot prohibited by secure boot policy SecureBoot UEFI Secure Boot state: %s grub_boot_time_head grub_efi_finish_boot_services grub_efi_get_secureboot grub_real_boot_time grub_reboot (,gpt3)/boot/grub # ubuntu上的grub-install就不一样,默认找当前esp分区的/EFI/ubuntu/下的文件,写死的 => strings /boot/efi/EFI/GRUB/grubx64.efi | grep -i efi ... /EFI/ubuntu
如果引导没进去,打开了GRUB命令行,可能是找不到grub.cfg。可以查看一下prefix的值并找一下里面有没有grub.cfg,可能目录错了
1 2 grub> echo $prefix (hd0,gpt3)/EFI/ubuntu # ubuntu的efi引导第一个prefix是这个
3) 加载$prefix/grub.cfg,进入grub引导 然后就是grub的操作了,UEFI的工作到此为止
2. legacy引导 Legacy引导是传统的BIOS引导方式,也称为BIOS引导或CSM(Compatibility Support Module)引导。与UEFI相比,legacy引导方式更为简单,但也有其局限性。
1) MBR分区表 Legacy引导使用MBR(Master Boot Record)分区表,而不是GPT。MBR有以下特点:
MBR位于磁盘的第一个扇区(512字节) 包含引导代码和分区表(最多4个主分区) 支持的最大磁盘容量为2TB 分区表占用64字节,每个分区表项16字节 1 2 3 4 5 6 7 8 9 10 11 => sudo fdisk -l /dev/sda Disk /dev/sda: 465.76 GiB, 500107862016 bytes, 976773168 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x000a8b6a Device Boot Start End Sectors Size Id Type /dev/sda1 * 2048 999423 997376 487M 83 Linux /dev/sda2 999424 976773119 975773696 465.3G 83 Linux
2) 引导过程 Legacy引导过程如下:
BIOS自检(POST) BIOS查找启动设备(按照CMOS中设置的启动顺序) 读取启动设备的第一个扇区(MBR)到内存0x7C00处 跳转到0x7C00执行MBR中的引导代码 MBR引导代码查找活动分区 加载活动分区的引导记录(如GRUB stage1) 执行引导加载程序(如GRUB),加载操作系统 3. 制作一个支持legacy+UEFI双引导的U盘或系统盘 制作一个同时支持Legacy和UEFI双引导的U盘或系统盘,结合两种引导方式的特点,按照下面的步骤执行。
1) 分区方案 由于uefi一定要使用gpt分区方式,而legacy重点还是在引导分区,所以我们使用gpt方式来进行分区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 => sudo fdisk /dev/sdX # sdX是你的U盘或硬盘设备 # 创建GPT分区表 Command (m for help): g Created a new GPT disklabel (GUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX) # 创建EFI系统分区(ESP),512M比较合适 Command (m for help): n Partition number (1-128, default 1): First sector (2048-XXXXXXXX, default 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-XXXXXXXX, default XXXXXXXX): +512M Created a new partition 1 of type 'Linux filesystem' and of size 512 MiB. # 设置ESP分区类型,这里是重点,一定要改这个分区的type 为efi,也就是esp标记分区 Command (m for help): t Partition number (1-128, default 1): Partition type (type L to list all types): 1 Changed type of partition 'Linux filesystem' to 'EFI System'. # 创建BIOS启动分区(用于Legacy引导),1M即可,不用格式化 Command (m for help): n Partition number (2-128, default 2): First sector (XXXXXXX-XXXXXXXX, default XXXXXXX): Last sector, +/-sectors or +/-size{K,M,G,T,P} (XXXXXXX-XXXXXXXX, default XXXXXXXX): +1M Created a new partition 2 of type 'Linux filesystem' and of size 1 MiB. # 设置BIOS启动分区类型,这个是legacy启动需要识别的分区类型 Command (m for help): t Partition number (2-128, default 2): Partition type (type L to list all types): 4 Changed type of partition 'Linux filesystem' to 'BIOS boot'. # 配置其他分区 # 保存并退出 Command (m for help): w The partition table has been altered.
2) 格式化分区 1 2 # 格式化ESP分区为FAT32 sudo mkfs.fat -F32 /dev/sdX1
3) 安装GRUB2引导程序 1 2 3 4 5 6 7 8 # 挂载分区 sudo mount /dev/sdX1 /boot/efi # 安装GRUB2 for UEFI,后面的id 用于定义引导名字,在ubuntu低版本下,这个id 必须用ubuntu,因为写死了 sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id xxx # 安装GRUB2 for Legacy sudo grub-install --target=i386-pc --boot-directory=/boot /dev/sdX
5) 配置GRUB 1 2 # 生成GRUB配置文件 sudo grub-mkconfig -o /boot/grub/grub.cfg
然后基本就可以用了,剩下的就看测试效果具体排查了。部分系统可能要关闭secure boot才能启动U盘里的系统
小技巧和踩坑记 1. 报错[xxxx] pcieport 0000:00:1c.5 xxxx刷屏,无法关机 在/etc/defaults/grub里面修改,在GRUB_CMDLINE_LINUX_DEFAULT=后面追加splash pci=nomsi 2. 报错perl: warning: Falling back to the standard locale ("C"). 1 2 # 这个可以解决不识别 zh_CN.UTF-8 sudo apt get install language-pack-zh-hans
3. 分辨率怎么调都不行,总是达不到1080 可以考虑下是线的问题,被一根VGA线坑了一年的人真诚提醒 4. 中文乱码 部分中文出现乱码很有可能时中文字体没有安装,一般安装wqy-microhei就可以解决 5. vmware在linux下偶现虚拟机卡死 6. 调整分区大小导致无法mount分区 使用分区管理器调整大小的时候,如果连续调整同时生效,很有可能出现调整失败,原分区也无法mount的情况 报错提示超级块里面的大小和分区表的大小不一致 解决 使用parted命令处理分区,使用resizepart将分区大小调整到原来的大小,和报错里面的一致就基本可以了 7. 查看机器内存情况 下面命令查看几个插槽插了几个内存条 可以看到四个插槽,插了两个DDR4的16G内存,速度3200 MT/s 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 => sudo dmidecode | grep -A16 "Memory Device$" Memory Device Array Handle: 0x003B Error Information Handle: Not Provided Total Width: Unknown Data Width: Unknown Size: No Module Installed Form Factor: Unknown Set: None Locator: Controller0-ChannelB-DIMM0 Bank Locator: BANK 1 Type: Unknown Type Detail: None -- Memory Device Array Handle: 0x003B Error Information Handle: Not Provided Total Width: 64 bits Data Width: 64 bits Size: 16 GB Form Factor: DIMM Set: None Locator: Controller0-ChannelA-DIMM1 Bank Locator: BANK 0 Type: DDR4 Type Detail: Synchronous Speed: 3200 MT/s -- Memory Device Array Handle: 0x003B Error Information Handle: Not Provided Total Width: Unknown Data Width: Unknown Size: No Module Installed Form Factor: Unknown Set: None Locator: Controller0-ChannelB-DIMM0 Bank Locator: BANK 1 Type: Unknown Type Detail: None -- Memory Device Array Handle: 0x003B Error Information Handle: Not Provided Total Width: 64 bits Data Width: 64 bits Size: 16 GB Form Factor: DIMM Set: None Locator: Controller0-ChannelB-DIMM1 Bank Locator: BANK 1 Type: DDR4 Type Detail: Synchronous Speed: 3200 MT/s
8. UEFI启动进入grub命令行 在grub命令行 执行下面命令启动
1 2 3 4 5 6 7 # 设置根目录,对应的hdx,y要用ls (hdx,y)/看是不是你对应的目录 set root=(hdx,y) # 设置grub安装路径,也就是前缀 set prefix=(hdx,y)/boot/grub # 挂在normal来启动normal insmod normal normal
进入系统后执行下面命令修复
1 2 3 4 update-grub # uefi启动的按照下面这样设置,会生成到/boot/efi/EFI/GRUB/下两个文件 grub-install --target x86_64-efi --efi-directory=/boot/efi --bootloader-id GRUB
修复后如果还是一直进GRUB,可能是prefix的问题,在GRUB命令行下执行 1 2 grub> echo $prefix (hd0,gpt3)/EFI/ubuntu # ubuntu的efi引导第一个prefix是这个,后面改成/boot/grub
看一下默认的prefix和当前的bootloader-id是不是不一致,不一致换对应的bootloader-id即可
1 grub-install --target x86_64-efi --efi-directory=/boot/efi --bootloader-id ubuntu