【notes4】linux命令/工具,systemd,文件操作,动静态库,信号,socket,std&boost,raii,智能指针,引用,完美转发
1.linux命令
nohup java -jar a.jar产生nohup.out(不是脚本输出打印而是异常打印)
netstat -ntlp查看运行程序的端口。
kill -9 %1(这个1就是jobs命令的1编号)。
top -d/n 1
内存条:dmidecode |grep -P -A5 "Memory\s+Device"|grep Size|grep -v Range
ps -ef | grep memtester | grep -v grep | cut -c 9-16 | xargs kill -9
echo b > /proc/sysrq-trigger # 强制重启(echo 1 > /proc/sys/kernel/sysrq激活,不保存数据,直接终止所有进程)
reboot -d 60 # 表示60秒后重启,发送SIGTERM信号通知进程保存数据
shutdown -r +5 # 系统将在5分钟后重启
shutdown -r now # 立即重启
rm -rf !(bak)
du -sh openbmc
scp -r -P
chown -R a:a ... # change owner(更改所有者), chmod: change mode(更改权限)
sudo rsync -av --exclude='build' openbmc/ /home/a/openbmc # 拷贝
grep -ne webui-vue -r --exclude-dir=build
find . -ls | grep 'usr/bin/'
find . -iname a -not -path "./build/*"
1.1 用户管理及权限
useradd -m -s /bin/bash john (/bin/sh: chsh -s /bin/bash john),passwd john
vi /etc/sudoers, a ALL=(ALL:ALL) ALL , wq!保存, a用户能切到root用户。p是指管道文件。


1.2 find

日志文件没清空非常大,要找到删除,如下找系统中大文件,超过10M。
如下查找文件夹,文件夹有相应名字或大小属性。
如下基于修改时间,time是天。-1:今天一天之内。1:1天前这一天。+1:1天前。


如下指定最大文件深度。
如下是find指令总结。
1.3 grep

如下* 可换成 * .txt。
-r:递归子路径,-n:显示行号。-i:忽略大小写。
如下用于java日志文件非常大,要grep出某个异常如ioexception,且需要打印出exception下面几行看什么出了错。

如下.中的\是转义符,.168这样重复3次(注意168前面有.,不是168,而是数字)。

1.4 cat/more
cat的文件非常大,非常占用cpu和内存,这时候可以每次读取一小部分。
如下通过空格往后翻页。
如下指定从第四行开始读。
如下查看前后10行。

1.5 head/tail

如下打印文件最后两行,tail -f 阻塞监控。
df -h查看磁盘使用,占用率太高就需要使用前面find,grep指令并进行删除。
题目:输出当前路径及当前路径子路径下所有.txt文件,要求大小超过1M,并且按照从大到小顺序进行排序输出前10个?
先通过find . -name '*.txt' -size +1M -type f 查看是否有大于1M的txt文件,没有的话就不用继续了。
再通过find . -name '*.txt' -size +1M -type f -print0|xargs -0 du -m|sort -nr|head -10。
1.6 打包和解压
# tar只是打包,并不压缩或解压
-f:--file
-c: 建立,create
-x:解压,eXtract
-t:查看内容
-j:--bzip2
-z:--gzip
-v:--verbose,详细列出处理过的文件

1.7 正则
^[]$:中括号内部只匹配一个字符。\d,?* +这些是[a-z]{m,n}这些的简写。
\d:相等于[0- 9],中括号里是什么或什么。\D:相等于[^0- 9],除了0到9外的任意字符。如下匹配数字\d 或 数字外任意字符\D(也就是匹配任意字符)。
\w:字母,数字或下划线,常用于用户名的命名上。


组group:如上只想获取@前面的用户名,上面中括号,大括号都出现了,就差如下小括号。first就是组名,右边是js语法,underfined因为没命名。


1.8 sed
如下将逗号替换为空格。s表示替换,g表示全局,即行中所有匹配项都被替换。sed -i ‘s/sys_led/sys_url/g’ /home/sysfs1/s3ip_sysfs_frame/sysurl_sysfs.c。
^匹配行首,$匹配行尾,如下d删除空行或只包含空格的行(因为行首行尾中间为空)。
sed常用于管道过滤,如下把x替换成y。
如下-r打开扩展正则,将逗号换成TAB。sed -n ‘60,70p’ a.txt :查看文件第60-70行。
1.9 awk
如下按逗号分隔并打印分割后的第三列和第四列,默认用空格/制表符/换行符作为字段分隔符。-F指定分隔符,-V设置变量,NF列数,$NF是一行数据最后一列的值,多用于对字段(像数据库中的字段)。
如下BEGIN指定了处理文本之前需要执行的操作。END指定了处理完所有行之后所需要执行的操作。
如下按逗号分隔并打印最后一列内容。

对比上面,如下一行一行读取并打印,列数>0打印。


如下-c统计个数。
如下2是个数,不是序号。

1.10 shell语法
unset删除变量。

获取字符串长度:echo ${#str} ,expr length “${str}”。
截取字符串:echo ${str:1:4}:显示字符串第1到第4个字符。echo ${str:4}:从左边第4个字符开始,一直到结束。echo ${str:0-6:3}:从倒数第6个字符开始的3个字符。echo ${str:0-6}:从倒数第6个字符开始,一直到结束。
file=/dir1/dir2/dir3/my.file.txt
${file#*/}:删掉第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt / 可换成 . 即#*.
${file##*/}:删掉最后一个 / 及其左边的字符串:my.file.txt
${file%/*}:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:删掉第一个 / 及其右边的字符串:(空值)
# "/sys/bus/i2c/drivers/xdpe12284/29-0040/hwmon/hwmon*/in2_input" , *当成字符串, 不匹配
DRIVER_PATH="/sys/bus/i2c/drivers/xdpe12284/29-0040/" # 最后加上 /
VOUT1_NODE="in2_input"
vout1_node=$(find "$DRIVER_PATH" -name "$VOUT1_NODE")


#!/bin/bash
for i in `ifconfig | grep -o ^[a-z0-9.]*`
do
name=$i
echo $name
ipaddr=$(ifconfig $i|sed -n 2p|awk '{ print $2 }'|tr -d 'addr:') # -d删除
echo http://$1:8080/api/slave -d '{"slave":"'$2${name}'","ip":"'${ipaddr}'"}'
done

输入字符大小写转换:

删除字符:
压缩字符:
如下c取反,删除非:
#!/bin/bash
while true
do
memtester 1G 5 & # ssd用fio
stress -c 10 -t 600 &
sleep 600
data=$(ps aux | grep stress | awk -F ' ' '{print $2}')
echo "$data" | while IFS= read -r line; do
kill -9 "$line"
done
# pkill -9 -f "stress|memtester" # pkill -9 -f "microcom|mTerm_client
sleep 600
done
cat /var/log/c | while IFS= read -r line; do
if [ "$line" != "gpiochip0 193" ] && [ "$line" != "gpiochip0 93" ] ; then
echo "$line"
fi
done
cat /var/log/d | grep -vE "^3[0-9]{3}$" # 排除文件中3111这类数字
if [[ $a =~ all ]]; then # 同 if [[ $a == *all* ]]; then
echo "a 包含 'all'"
fi
如下截取 = 号左边即第一个。shell中与&&,或||。
"-d")
shift # 默认左移一位
case ${1} in
"lc1" | "lc2" | "cmm" | "fb1" | "fb2")
;;
*)
usage
;;
esac
dev=${1}
led_devie=${dev%[^a-zA-Z]} # lc
if [ "$1" != "cmm" ];then
index=${dev/*[a-zA-Z]/} # 1
fi
;;



# shift.sh , $# 是可执行程序后面参数个数,不包含可执行程序
until [ $# -eq 0 ]
do
echo "第一个参数为: $1 参数个数为: $#"
shift
done
$./shift.sh 1 2 3 4
第一个参数为: 1 参数个数为: 4
第一个参数为: 2 参数个数为: 3
第一个参数为: 3 参数个数为: 2
第一个参数为: 4 参数个数为: 1
basename /usr/local/nginx/conf/nginx.conf
nginx.conf
basename -s .conf /usr/local/nginx/conf/nginx.conf
nginx
dirname //
/
dirname /a/b/
/a
dirname a
.
dirname a/b
a
realpath $path : 返回$path的绝对路径,路径不存在会报错,文件不存在不会报错
root@bmc:~# parameter_str=$(fruid-util | head -n 1 | awk -F "[" '{print $2}')
root@bmc:~# echo $parameter_str
all, ocm, bmc, base, mac, fcb, fan1, fan2, fan3, fan4, fan5, fan6 ]
root@bmc:~# parameter_r_arr=${parameter_str//,/ } #逗号替换为空格
root@bmc:~# echo $parameter_r_arr
all ocm bmc base mac fcb fan1 fan2 fan3 fan4 fan5 fan6 ]
root@bmc:~# parameter_r_arr=(${parameter_str//,/ })
root@bmc:~# echo $parameter_r_arr
all
root@bmc:~# unset "parameter_r_arr[-1]" #删除最后 ]
root@bmc:~# echo $parameter_r_arr
all
root@bmc:~# echo ${parameter_r_arr[@]}
all ocm bmc base mac fcb fan1 fan2 fan3 fan4 fan5 fan6
let和$(( ))和expr用法类似,都是用来对整数进行运算,不能对小数(浮点数)或字符串运算。

a=2
a+=1 # 拼接
echo $a # 21


declare -i index=${1: -1}
echo $index
# ./a.sh
0
# ./a.sh ls1 #取最后一个int类型数字
1
/dev/null是一个特殊文件,写入到它的内容都会被丢弃,从该文件读取内容也什么也读不到。




a(){
return 0 # return其他值,下面用if ! a
}
if a ; then # if后面必须是表达式,0是真,没有if 0
echo "111" # 打印
fi
// 如下是C语言,python一样。 if [ -n str1 ] 当字符串的长度大于0时为真(字符串非空) 。
if (0) { // 0不打印,其他值都打印,同编译#if 0
printf("111");
}
unsigned char a = 0x01;
if ( a ) {
// a = 0x01 , "0x00" , "0x01"
}
else {
// 0x00
}
while(1!=2); //真,运行后一直阻塞
command > /dev/null 2>&1不在屏幕上显示输出结果和错误。
# 检测shell 语法1:shellcheck ./
# 检测Python语法1:python3 -m flake8 ./
# 检测Python语法2:python3 -m black ./
# 检测C语法 :cppcheck 4 -q --enable=all ./
# 服务器做CppCheck时,需要过滤一些Check的文件。比如MakeFile、证书文件等
filter_make_file="Makefile"
filter_pem_file=".pem"
count=-1
for value in ${files_array[@]} # files_array变量包含了所有要检测的文件(过滤前)
do
if [[ $value =~ $filter_make_file || $value =~ $filter_crt_file ]]
then
continue
fi
count=$[$count+1]
new_files_array[$count]=$value # 将检测文件(过滤后)加入新的数组中
done
psu_model_check()
{
for((i=0;i<"${PSUNUM}";i++))
{
val=$(get_psu_present $((i + 1)))
if [ "${val}" -eq 0 ];then #present
PSU_SYSFS_DIR=$(i2c_device_sysfs_abspath "${PSU_PMBUS_SLAVE_ADDR_ARRAY[$i]}")
psu_model_value[$i]=$(head -n 1 "${PSU_SYSFS_DIR}/mfr_model")
else
psu_model_value[$i]="NA"
fi
}
for((i=0;i<"(${PSUNUM}-1)";i++))
{
if [ "${psu_model_value[$i]}" == "NA" ];then
continue
fi
if [ "${psu_model_value[$i]}" != "${psu_model_value[$((i+1))]}" ];then
psu_model_log_flag[$i]=true
else
psu_model_log_flag[$i]=false
fi
if [ "${psu_model_log_flag[$i]}" != "${psu_model_last_log_flag[$i]}" ]; then # 这行last表示上一次,作用是状态不变就不记录
psu_model_last_log_flag[$i]="${psu_model_log_flag[$i]}" # 这行last表示最新
if ${psu_model_log_flag[$i]} ; then
logger -p user.warning -t "$PROGRAM" "Abnormal: PSU Model Different: $i:${psu_model_value[$i]},$((i+1)):${psu_model_value[$((i+1))]}"
else
logger -p user.info -t "$PROGRAM" "Normal: All PSU Model Same: $i:${psu_model_value[$i]},$((i+1)):${psu_model_value[$((i+1))]}"
fi
fi
}
}
#!/bin/bash
IP=$1
FILE1_PATH=$2
FILE2_PATH=$3
MAX_ATTEMPTS=$4
prog=$(basename "$0")
if [ "$#" -ne 4 ]; then
echo "Usage: $prog IP FILE1 FILE2 MAX_ATTEMPTS"
echo "Example: $prog 10.130.6.1 xg2_nvme_bp_cpld_v1.04_forobmc.tar.gz xg2_6nvme_cpld_v0_00_02_forobmc.tar.gz 10"
exit 1
fi
token=`curl -k -H "Content-Type: application/json" -X POST https://$IP/login -d '{"username" : "root", "password" : "root"}' 2>&1 | grep token | awk '{print $2;}' | tr -d '"'`
if [ -z "$token" ]; then
echo "[ERROR] Authentication failed"
exit 1
fi
for ((i=1; i<=$MAX_ATTEMPTS; i++))
do
echo -e "\n========= 第 ${i} 次升级尝试 ========="
result1=$(curl -sik \
-H "Expect:" \
-H "Content-Type: multipart/form-data" \
-H "X-Auth-Token: $token" \
-F "UpdateFile=@\"$FILE1_PATH\";type=application/octet-stream" \
"https://$IP/redfish/v1/UpdateService/update-multipart" 2>&1
)
result1_error=`echo $result1 | grep error`
if [ -z "$result1_error" ]; then
sleep 30
result2=$(curl -sik \
-H "Expect:" \
-H "Content-Type: multipart/form-data" \
-H "X-Auth-Token: $token" \
-F "UpdateFile=@\"$FILE2_PATH\";type=application/octet-stream" \
"https://$IP/redfish/v1/UpdateService/update-multipart" 2>&1
)
result2_error=`echo $result2 | grep error`
if [ -z "$result2_error" ]; then
sleep 30
continue
else
echo $result2
break
fi
else
echo $result1
break
fi
done
# 案例:清空文件夹但保留特定内容
#!/bin/bash
# 要清理的目标目录
target_dir="./A"
# 要保留的目录/文件白名单
keep=(
"./A/B/C"
"./A/E.txt"
)
# 根据 keep ,生成要保护的父路径列表,装在 extra_keep 中
# 比如 keep 中写明了 ./A/B/C ,那就得额外确保 ./A/B 和 ./A 不会被删除,因为它们也是会出现在 find 的结果里的
# 不知怎么的,GPT 就是考虑不到这点
add_parent_path() {
local path="$1"
# target_dir 不在这里保护,其由最下面的 -mindepth 1 保护
while [ "$path" != "$target_dir" ]; do
extra_keep+=("$path")
# 去掉路径的最后一段,得到父路径
path=$(dirname "$path")
done
}
for item in "${keep[@]}"; do
add_parent_path "$item"
done
# 构建 find 命令的排除参数
# 对于 keep ,既要保护文件/目录本身,又要保护其子目录(如果有的话)
# 比如 keep 中写明了 ./A/B ,那 ./A/B/C 就不能被删除掉
exclude_params=()
for item in "${keep[@]}"; do
# 这里的 -prune 起到了排除子目录的效果
exclude_params+=(-path "$item" -prune -o)
done
# 对于 extra_keep ,只需要保护目录本身不被删除即可,无需保护子目录
for item in "${extra_keep[@]}"; do
exclude_params+=(-path "$item" -o)
done
# 打印被删除的文件/目录列表
find "$target_dir" -mindepth 1 "${exclude_params[@]}" -exec echo '{}' +
# 利用 find 命令执行删除
# -mindepth 1 是必要的,因为 extra_keep 并不会保护到 target_dir ,它被删了就前功尽弃了
find "$target_dir" -mindepth 1 "${exclude_params[@]}" -exec rm -rf '{}' +

1.11 i2ctransfer
dd if=/dev/zero of=a bs=10M count=1 2> b (创建10M的a文件,控制台打印的log到b文件,b内容是“1+0 records in …”)。
# openbmc_write_rtk_with_bin.sh [file] <busid> # rtl芯片和switch芯片相关
# [file] : binary file" 要写入的二进制文件: RTL8364NB-VB_EE_PATCH_V1.4_ID1_hua_20210810.bin
# <busid> : busid used for i2ctransfer" # i2c设备的总线号:3
#!/bin/sh
# 如下将二进制bin文件转为十六进制(0xff)文本文件再写入/tmp/excute_rtk.bin或excute_rtk.txt
# $1:RTL8364NB-VB_EE_PATCH_V1.4_ID1_hua_20210810.bin $2:/tmp/excute_rtk.bin
pad_hexdata(){
binsize=$(stat -c "%s" $1) # %s 文件大小(单位是字节byte)
remainder=$[$binsize%16] # % :除以16字节取余 , itransfer一行写16字节
if [ $remainder -ne 0 ];then # 比如33字节,第一行16,第二行16,第三行1
declare -i pad_num=16-$remainder
dd if=/dev/zero bs=$pad_num count=1 | tr "\000" "\377" >> $1 # 二进制文件不足的 我要这一行补0xff ,tr命令把00转换成ff
fi
touch /tmp/t.txt /tmp/p.txt
cat $1 | od -x -v | awk '{$1=null;print $0}' |sed '/^$/d' |sed s/[[:space:]]//g |sed 's/.\{2\}/& /g' > /tmp/t.txt # od -x -v和hexdump一样用16进制查看bin文件
cat /tmp/t.txt | awk '{print " " $2 " " $1 " " $4 " " $3 " " $6 " " $5 " " $8 " " $7 " " $10 " " $9 " " $12 " " $11 " " $14 " " $13 " " $16 " " $15}' > /tmp/p.txt
sed s/[[:space:]]//g /tmp/p.txt |sed 's/../0x&/g'|sed 's/.\{4\}/& /g' > $2
rm /tmp/t.txt /tmp/p.txt
}
# 如下生成/tmp/excute_rtk.sh ,每一行前面拼接i2ctransfer,i2ctransfer将上面/tmp/excute_rtk.bin内容写入设备
pad_shell(){ # $1:/tmp/excute_rtk.sh $2:3
offset=0
while read line
do
hex_offset=$(printf "%04x" $offset)
hex_offset_h=${hex_offset: 0: 2}
hex_offset_l=${hex_offset: 2: 2}
echo "i2ctransfer -f -y ${2} w18@0x50 0x$hex_offset_h 0x$hex_offset_l $line" >>$1
offset=$((${offset}+16))
done < $d_path_hex
sed -i '1i #!/bin/sh' $1
}
do_action(){ # $1 : RTL8364NB-VB_EE_PATCH_V1.4_ID1_hu_20210810.bin $2 : 3
busid=$2
s_path=$1
d_path_hex="/tmp/excute_rtk_bin"
d_path_sh="/tmp/excute_rtk.sh"
if [ -f ${d_path_hex} ];then
rm ${d_path_hex}
fi
if [ -f ${d_path_sh} ];then
rm ${d_path_sh}
fi
touch ${d_path_sh}
touch ${d_path_hex}
pad_hexdata ${s_path} ${d_path_hex}
echo "create $d_path_hex success!!"
pad_shell ${d_path_sh} ${busid}
echo "create $d_path_sh success!!"
sh ${d_path_sh} # /tmp/excute_rtk.sh将/tmp/excute_rtk_bin内容写入设备
}
usage(){
program=$(basename "$0")
echo "Usage:"
echo "$program [file] <busid>"
echo " [file] : binary file"
echo " <busid> : busid used for i2ctransfer"
echo "Note:"
echo " generate /tmp/excute_rtk_bin.sh based on binary file then execute shell"
echo "Examples:"
echo " $program RTL8364NB-VB_EE_PATCH_V1.4_ID1_huaqin_20210810.bin 3 "
echo ""
}
check_parameter(){
if [ $# != 2 ];then
usage
exit 0
fi
if [ ! -f $1 ];then
echo "$1 doesnot exist"
exit 0
fi
}
check_parameter "$@"
do_action "$@"

# hexdump -e '8/1 "0x%02x, " "\n"' test.bin > xxd.hex # 格式化输出binary二进制文件
# cat xxd.hex
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xfe,
0x01, 0x06, 0x19, 0xc9, 0x42, 0x69, 0x67, 0x56,
0x65, 0x6e, 0x64, 0x6f, 0x72, 0xc5, 0x4d, 0x79,
0x42, 0x4d, 0x43, 0xc4, 0x41, 0x41, 0x42, 0x42,
0xc4, 0x43, 0x43, 0x44, 0x44, 0xc4, 0x45, 0x45,
0x46, 0x46, 0xc4, 0x48, 0x48, 0x47, 0x47, 0xc0,
0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61,
# xxd -r -p xxd.hex xxd.bin # 使用xxd转成binary 文件, 检查md5一致
1.12 expect
# remote_pdu_cycle.sh
#!/bin/sh
cmd_link="curl -X get http://240.1.1.1:8080/api/bmc/info | grep refused"
cmd_version="curl -X get http://240.1.1.1:8080/api/bmc/info | grep A-CM-v1.03.00"
max_count_test=5000
j=0
do_action() {
test_count=6
i=0
while [ $i -lt $test_count ]; do
result=$(sshpass -p 0penBmc /usr/bin/ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.55.205.180 $cmd_link)
if [ -z "$bmc_ver" ]; then
time_value=$(date)
echo "$time_value: restful ok" >> ./pdu.log
break
fi
let i+=1
sleep 30
done
if [ $i -ge $test_count ]; then
time_value=$(date)
echo "$time_value: restful fail" >> ./pdu.log
exit 1
fi
result=$(sshpass -p 0penBmc /usr/bin/ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@10.55.205.180 $cmd_version)
if [ -z "$result" ]; then
echo "$time_value: $result version fail" >> ./pdu.log
exit 1
else
echo "$time_value: $result version ok" >> ./pdu.log
return 0
fi
}
while [ $j -lt $max_count_test ];
do
echo "==============LOOP $j==============" >> ./pdu.log
date >> ./pdu.log
echo "power off 22,23" >> ./pdu.log
./remote_pdu_cycle_off.sh
sleep 10
echo "power on 22,23" >> ./pdu.log
./remote_pdu_cycle_on.sh
sleep 300
do_action
date >> ./pdu.log
let j+=1
done
# remote_pdu_cycle_off.sh
#!/usr/bin/expect
set timeout 200000
set passwd 123456
set name apc
set passwderror 0
# spawn ssh-keygen -f "/home/morton/.ssh/known_hosts" -R $IP
# spawn scp $local_file /usr/local/morton/bmc_upgrade_for_ALI.sh root@$IP:~
spawn telnet 10.75.135.100
expect {
"*User Name*" {
send "$name\r"
exp_continue
}
"*Password*" {
send "$passwd\r"
exp_continue
}
"*apc>*" {
sleep 1
send "oloff 22,23\r"
sleep 1
exit 1
}
timeout {
puts "connect is timeout"
exit 3
}
}
# b文件:llength等是expect中专属的,和ftp那些命令一样。
#!/usr/bin/expect
puts "$argv0"
set arg_leng [llength $argv]
puts "$arg_leng"
set argv_0 [lindex $argv 0]
puts "$argv_0"
# @obmc-server:~/test$ ./b w
# ./b
# 1
# w
2.工具
# samba:/etc/samba/smb.conf ,systemctl restart smb ,smbpasswd -a a(将本地用户转变为samba用户即共享用户)
[a-home]
comment = private share folder
path = /home/a
writable = yes
browsable = yes
valid users = a
# writeable=no指除了下行外都不能写。[a-home]是共享名即对方能看到的网络文件夹名称,comment是可共享目录的描述信息(可加可不加),valid_users指谁可以访问。
# windows -> 此电脑 -> ... -> 映射网络驱动器 -> \\ip\a-home
su root(需输入root密码)。sudo su/-i(当前用户暂时申请root权限,需输入当前用户密码)。https://smallpdf.com/pdf-to-jpg。选择文件夹右击"授权访问权限",选择"特定用户",添加"所有人",权限改为"读取与写入",以共享方式访问,就可删除文件夹了,还有删除里面文件就可删除文件夹。
curl ipinfo.io 查公网出口ip。
https://www.usb-drivers.org/ft232r-usb-uart-driver.html。
1.内存卡插入读卡器再插入电脑的usb接口,将u盘格式化为FAT32格式。如果右击格式化没有FAT32则用格式化fat32工具:http://www.ridgecrop.demon.co.uk/guiformat.exe。树莓派系统官网:https://www.raspberrypi.org/downloads/raspbian/。下载系统(1.9G)用写盘工具(https://www.balena.io/etcher/或win32diskimager)将系统写入u盘,写完后不要格式化其他盘。
2.树莓派插上内存卡,接上网线(网络认证)或连上wifi(连wifi,eth0信息为空,因为没有连接网线,wlan0显示内网ip)用ifconfig查看网络。service ssh start开启ssh服务,树莓派默认用户名pi,密码raspberry,sudo netstat -antp可查看到当前有22端口即ssh端口被监听,这时可关闭显示器。如果忘了树莓派内网ip,可用win下cmd中输入arp -a查看局域网下所有ip和mac对应。本电脑就可以ssh连接树莓派再通过picocom -b 115200 /dev/ttyUSB0(有实际的线连)连接到机器终端,minicom -D /dev/ttyUSB0 -b登不上时进入/var/lock,删除lockfile,minicom又可正常启动。
Ubuntu镜像网站http://mirrors.ustc.edu.cn/ubuntu-releases/16.04/。软碟通Ultraiso将从itellyou下载的win10或ubuntu镜像写入U盘。
bios启动过程按F7进入如下设置,UEFI启动U盘如下(legacy为老版本不用)。进入u盘引导选择install ubutu:安装过程如果出现/dev/sda一般是U盘,SSD一般为/dev/nvmeon1。设置时间,联网,apt-get install vim。bios启动依赖cpu和dimm。
vscode连接远程服务器报XHR failed:https://update.code.visualstudio.com/commit:d037ac076cee195194f93ce6fe2bdfe2969cc82d/server-linux-x64/stable (切换浏览器下载),拷贝进远程服务器解压如下:
2.1 pip换源
pip install速度慢,用pip换源:进入python,看os在哪个py脚本里即python工作路径在1下,所以在1下建pip文件夹可以执行。
在桌面新建pip文件夹再拖进1,在pip里新建.txt,再改为.ini文件,点击打开输入下面代码保存。
apt install python3-pip --fix-missing(断开vpn,cp /etc/apt/sources.list /etc/apt/sources.list.bak,python3 -m pip install --upgrade pip,apt-get update,pip install 包名 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com)
三个源:https://pypi.tuna.tsinghua.edu.cn/simple,https://pypi.douban.com/simple,https://mirrors.aliyun.com/pypi/simple,upgrade升级下numpy,看下载速度:pip install numpy -U
以上为windows换源,下面为ubuntu换源同理:cd ~,mkdir .pip,cd .pip,gedit pip.conf,将以下内容复制到pip配置文件pip.conf中,保存退出即可。
[global]
index-url = http://mirrors.aliyun.com/pypi/simple/
[install]
trusted-host = mirrors.aliyun.com (上面http地址中间一部分,这行为了获取ssl证书,不然会报错)
2.2 环境变量
/etc/profile(所有用户永久生效)。用户目录下.bash_profile(单一用户永久生效,.bash_profile中都含有export,用户一登录就执行.bash_profile)。export命令(只对当前shell临时生效,用户登出再登录,env查看没有刚export的)。
# a.sh:
BASH_SOURCE="$_" # If using $_, it needs to be placed at the beginning of the script
echo "$0"
echo "$_"
echo "$BASH_SOURCE"
[ "$BASH_SOURCE" != "$0" ] && echo "不等于bash, 就是source"
$ source a.sh
/bin/bash # source命令本身是由/bin/bash执行的
/bin/bash
a.sh
不等于bash, 就是source
$ ./a.sh # 等于bash
./a.sh
./a.sh
./a.sh
# a.sh: echo "$0"
$ source ./././a.sh = . a.sh # 当前shell进程
/bin/bash
$ /bin/bash a.sh = bash a.sh = ./a.sh # 创建子shell进程
a.sh

chmod 777 /home/y/app/bin/build_bmc
~$ vi .bash_profile # 和windows里设置path环境变量一个道理
export PATH=/home/y/app/bin:$PATH
~$ vi .bashrc
alias gr='grep -nr'
!/bin/bash 直接指定应该去哪找bash
!/usr/bin/env bash 告诉系统去$PATH包含的目录中挨个去找,先找到哪个就用哪个
可执行程序都要PATH指定如ls,pwd(也是可执行程序)不加./执行,因为在冒号分隔(冒号不是连接)的几个目录下找。
当两个目录下都有同名可执行文件,执行哪一个取决于下面PATH先后顺序,env PATH=/usr/bin perl命令指定哪个执行。

如下/etc/profile.d/ 是目录,在sss.sh中写入export XXXX=‘cccc’。/etc/profile这个文件中有这么一段shell:for i in /etc/profile.d/*.sh;,会在每次启动时自动加载profile.d目录中每个配置即当用户重新登录shell如下或source /etc/profile 时会触发。
crontab


如下每星期每月每日每小时的第20到第40分钟每分钟都会执行一次(1小时执行21次)。

crontab -e进入如下,dow表示星期。
3.systemd
journalctl等带ctl都属于systemd,systemctl查看所有service状态,Restart=on failure是失败会继续执行,默认执行5次(同rc.d中sv restart sensor-mon)。
systemd 可以管理所有系统资源,不同的资源统称为Unit,一共分如下12种:systemctl --state failed | cat。

target是特殊的unit,它不执行任何实际的操作,作用把一组unit利用各种依赖和包含关系组织起来共同完成某件事情。systemd用目标(target类似状态标记) 替代了initd中运行级别的概念:
# systemctl get-default # 查询当前默认开机运行的target
multi-user.target
# systemctl cat multi-user.target
[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target # 要求basic.target一起运行
Conflicts=rescue.service rescue.target # 如果rescue.service或rescue.target正在运行,multi-user.target就不能运行,反之亦然
After=basic.target rescue.service rescue.target
AllowIsolate=yes # 允许使用systemctl isolate命令切换到multi-user.target
# service-a.service
Requires=service-b.service # 强依赖:b失败,a也失败
After=service-b.service # 强制a在b之后启动
# systemctl daemon-reload
# systemctl list-dependencies --type=service --no-pager # 查看依赖树
4.文件操作
4.1 文本文件
返回FILE结构体:fgets和fprintf以行方式读写文本文件,但不能读写二进制文件。用fread和fwrite可以读写文本文件和二进制文件。

vi /tmp/test1.txt,可见有5行记录,不管执行多少次都是5行记录,因为文件打开方式是w,每次打开文件时都会清空原文件中的记录。
如下301可换成sizeof(strbuf),300字符(字符串结束符\0)+1个换行符。
int main() {
int fd;
char buffer[5] = {0};
ssize_t bytesRead;
fd = open("/home_a/abcd", O_RDONLY, 0444);
bytesRead = read(fd, buffer, 4);
if (bytesRead == -1) {
printf("read error");
return 1;
}
printf("Read %zd bytes: %s\n", bytesRead, buffer); //Read 4 bytes: 0xff
return 0;
}
#include <stdio.h>
int main() { // a.c
char str[100];
printf("请输入一行字符串: ");
if (fgets(str, sizeof(str), stdin) != NULL) {
char *newline = strchr(str, '\n'); // strchr: 查找\n以及后面字符
printf("换行符:%s\n", newline);
if (newline != NULL) {
*newline = '\0'; //替换为结束符
}
printf("你输入的字符串是: %s\n", str);
return 0;
}
}

4.2 二进制文件





4.3 文件定位
文件内部有一个位置指针,用来指向当前读写的位置,也就是读到第几个字节。在文件打开时,如果打开模式是r和w,位置指针指向文件的第一个字节。如果打开模式是a,位置指针指向文件的尾部,每当从文件里读n个字节或文件里写入n个字节后,位置指针会后移n个字节。
FILE *fptime;
fptime=fopen("/tmp/time","w");
time_t time_log = time(NULL);
struct tm* tm_log = localtime(&time_log);
fprintf(fptime, "flag[%d] LINE[%d] %04d-%02d-%02d %02d:%02d:%02d\r\n",sensor_flag, __LINE__, tm_log->tm_year + 1900, tm_log->tm_mon + 1, tm_log->tm_mday, tm_log->tm_hour, tm_log->tm_min, tm_log->tm_sec);
fflush(fptime);
fclose(fptime);
FILE *fpLedColor=fopen(led_color,"w");
fseek(fpLedColor,0,SEEK_SET); // SEEK_SET:从文件开头开始计算偏移量
fprintf(fpLedColor,"%s",sensor_flag?LED_GREEN_CODE:LED_YELLOW_CODE);
fprintf(stderr,"11\n"); // 打印在控制台,cpp也可用
fflush(fpLedColor);
fclose(fpLedColor);
5.动静态库
编译【预处理(语法检查),编译(.c->.s汇编文件),汇编(.s->.o二进制文件),链接(多个.o合并成1个执行文件)】的最后阶段即将依赖引入过程叫链接,静态链接同【notes7】2.2章节,so打到二进制文件里,跨机器不用机器上有so文件。
动态链接的so文件通过mmap加载进共享内存,动态链接a.out文件小且内存占用小,此外动态链接在so库更新后不需重新编译,一般首选。很多进程用到C语言libc.so里stdio.h里打印函数,如果通过静态链接,每个二进制进程占一个libc.so,占用的内存多。
和内核交互除了syscall,大部分用库函数如libc,libc是一个标准有很多实现:g(gnu)libc【ubuntu】,musllibc【alpine】
如上默认编译链接是动态,如下static指定静态链接。gcc是gnu的编译工具集合,gcc不光编译c语言且支持很多平台
如下alpine系统没有glibc库。
5.1 静态库
公用函数库的public.cpp是源代码,对任何人可见,实际开发出于保密并不希望提供公用函数库源代码。C/C++提供了一个保证代码安全性方法,public.cpp编译成库。
// public.h
#ifndef PUBLIC_H
#define PUBLIC_H 1
#include <stdio.h>
void func(); // 自定义函数的声明
#endif
// public.cpp
#include "public.h"
void func() // 自定义函数的实现
{
printf("我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。\n");
}
// book265.cpp
#include "public.h" // 把public.h头文件包含进来
int main()
{
func();
}
g++ -o book265 book265.cpp public.cpp
./book265
我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。
链接库的文件名是libpublic.a,链接库名是public,缺点使用的静态库发生更新改变,程序必须重新编译
gcc -c -o libpublic.a public.cpp
使用方法一:直接把调用者源代码和静态库文件名一起编译:
g++ -o book265 book265.cpp libpublic.a
使用方法二:用L参数指定静态库文件的目录,-l参数指定静态库名:如果要指定多个静态库文件的目录,用法是“-L/目录1 -L目录2 -L目录3”;如果要指定多个静态库,用法是“-l库名1 -l库名2 -l库名3”。
g++ -o book265 book265.cpp -L/home/w/demo -lpublic
./book265
我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。
5.2 动态库
动态库发生改变,程序不需要重新编译,动态库升级方便。
g++ -fPIC -shared -o libpublic.so public.cpp
如果在动态库文件和静态库文件同时存在,优先使用动态库编译:
g++ -o book265 book265.cpp -L/home/w/demo -lpublic
执行程序./book265时,出现以下提示:/book265: error while loading shared libraries: libpublic.so: cannot open shared object file: No such file or directory,因为采用了动态链接库的可执行程序在运行时需要指定动态库文件的目录,Linux系统中采用LD_LIBRARY_PATH环境变量指定动态库文件的目录。采用以下命令设置LD_LIBRARY_PATH环境变量。
export LD_LIBRARY_PATH=/home/w/demo:.
如果要指定多个动态库文件的目录,用法是“export LD_LIBRARY_PATH=目录1:目录2:目录3:.”,目录之间用半角的冒号分隔,最后的圆点指当前目录。接下来修改动态库中func函数的代码:
// printf("我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。\n");
printf("生活美好如鲜花,不懂享受是傻瓜;\n");
如下重新编译动态库,无需重新编译book265,直接执行程序。
g++ -fPIC -shared -o libpublic.so public.cpp
./book265
生活美好如鲜花,不懂享受是傻瓜;
5.3 编译时为什么要加上 –lm
// 代码一
#include <stdio.h>
#include <math.h> //exp, man exp:Link with -lm
int main(int argc, char const *argv[]){
printf("The exponential value of %lf is %lf\n", 0, exp(0));
printf("The exponential value of %lf is %lf\n", 0+1, exp(0+1)); //e的1次幂
printf("The exponential value of %lf is %lf\n", 0+2, exp(0+2));
return(0);
}

// 代码二
#include <stdio.h>
#include <math.h>
int main(int argc, char const *argv[]){
double x = 0;
printf("The exponential value of %lf is %lf\n", x, exp(x));
printf("The exponential value of %lf is %lf\n", x+1, exp(x+1));
printf("The exponential value of %lf is %lf\n", x+2, exp(x+2));
return(0);
}


代码一调用exp传入的参数是常量为0 。代码二调用exp传入的参数是变量 x,代码一会不会在运行之前就计算好了呢?如下代码一没有看到调用exp的身影,当传入参数为常量时就已计算好了值,最后不需调用exp函数。代码二通过如下main.s汇编代码可见多次调用call函数。
math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,程序中用到的数学函数要到这个库文件里找。
gcc a.c -o a.out,arm-linux-gcc a.c -o b.out,如果执行out文件出现No such file or directory,则将如下两个so文件互相ln -s建软链接。
6.信号
getpid库函数功能是获取进程编号,该函数没有参数,返回值是进程的编号(相同程序在不同时间执行,进程编号不同)。



僵尸进程:一个进程执行了exit系统调用退出时会向父进程发送SIGCHLD信号,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态,如进程ID、终止状态等等)的进程,ps显示有< default >。父处理子进程死后的信息,如果不想处理就需要交给系统处理。
进程间通信IPC方式:1.管道:ls | grep。FIFO(first in first out)是管道一种。2.消息队列:内核创建的一个消息队列,os中多个进程都能操作这个消息队列,可以往里面发送消息,可以接收消息,类似socket。3.共享内存:每个进程访问内存时,有一个虚拟内存地址和物理内存地址的一个映射:一般两个进程的虚拟内存地址可以一样,但映射的物理内存地址一般不一样。共享内存就是将它们映射的物理内存地址也变一样,这时两个进程能同时访问一块相同的物理内存,借助这块物理内存实现通信。4.套接字socket:访问数据库进程和数据库进程本身,这两个进程间通信就是通过3306号端口建立起的tcp套接字。本机访问mysql不走tcp的套接字,而是走unix域套接字(UDS,不用ip)。5.信号量/灯:类似一个计数器,控制多个进程对一个共享资源的访问,起到控制数量的锁机制。6.信号:一个进程可向另一个进程发送一个信号,进程可处理这个信号。如下列出所有信号,linux中信号大多数是把另一个进程杀死,干脆把这个指令叫kill了,如下64种死法。 如键盘中断ctrl+c是当前shell向tail -f这个进程发送一个信号值为2的SIGINT的信号,如用kill+进程id命令是15,kill -9是9。
捕捉信号2:在死循环之前注册下信号的处理,如下让ctrl+c无效。
捕捉了ctrl+c,无法停止,只能用kill。

程序后台运行两种方法:&:ctrl+c无法中止,用killall book1或kill 进程号。if (fork()>0)return 0 父进程退出
信号作用:服务程序在后台运行,如果想终止它,杀了它不是个好办法,因为没有释放资源。如果能向程序发一个信号,程序收到这个信号后调用一个函数,在函数中编写释放资源代码。
下面 EXIT函数就是自定义函数,TcpServer设为全局变量因为EXIT函数要访问它并关闭socket。
7.socket
http(client和server间req和res),socket(client和server间长连接)。
accept第一个参数是listen fd,第二个参数是addr(如果不关心对端地址,直接填 NULL。非空时把对端 IP/端口填进去,格式跟bind一致),第三个参数是addrlen。listen fd是门卫,accept 把排到队的客人拉进来,发给你一张新门票(clientfd),以后只跟这张门票打交道。

一个字节是2的8次方即256,所以0-255。ipv4组合方式共2的32(4*8=32)次方即43亿个。B/S(浏览器对服务器)和C/S(客户端对服务器)。
下面将三要素融合,https中s就是secure。
如下文件上传:上面是服务端,socket封装了io流,所以要关。高层socket关了,低层也会关。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class UploadClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10086);
//11111111111111111111111111111111111111111111111111111111111111111111111111111
FileInputStream fis = new FileInputStream("柳岩.jpg"); //读本地文件
OutputStream os = socket.getOutputStream();
int length = -1;
byte[] buffer = new byte[1024];
while((length = fis.read(buffer)) != -1){ //读
os.write(buffer,0,length); //写到服务器socket中
}
fis.close();
socket.close();
}
}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
public class UploadServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(10086); //只创建一个服务端,一个端口不能被两个程序占用,不能在这行上面加while(true)
while(true){ //死循环接收客户端请求
Socket socket = serverSocket.accept(); //虽然while(true),但这行会阻塞,单线程
new Thread(new Runnable() { //不能放accept()前面,不能无限开线程
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
// long time = System.currentTimeMillis(); // 以系统当前时间命名,同一毫秒,有两个用户同时上传文件, 就会有用户文件覆盖
String name = UUID.randomUUID().toString().replace("-", ""); //下行time改为name
FileOutputStream fos = new FileOutputStream("c:/test/server/" + name + ".jpg");
int length = -1;
byte[] buffer = new byte[1024];
while((length = is.read(buffer)) != -1){
fos.write(buffer,0,length);
}
fos.close(); //应该放在finally中
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
// serverSocket.close(); // 服务器不能关闭,关了就不回到while(true)了
}
}
}
如下BS案例:socket被写,页面被读。写一个浏览器程序难度和操作系统一样,鸿蒙os就是浏览器。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class BsServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(10090);
Socket socket = serverSocket.accept(); //接收浏览器请求
//1111111111111111111111111111111111111111111111111111111111111111111111111111
OutputStream os = socket.getOutputStream(); //写出页面
os.write("HTTP/1.1 200 OK\r\n".getBytes()); // http协议的响应行 第一行
os.write("Content-Type:text/html\r\n\r\n".getBytes()); //http协议的响应头 第二行,第三行为空白
FileInputStream fis = new FileInputStream("index.html"); //已有,用来被读
int length = -1;
byte[] buffer = new byte[1024];
while((length = fis.read(buffer)) != -1){
os.write(buffer,0,length); //第四行,响应体
}
fis.close();
socket.close();
serverSocket.close();
}
}


8.std&boost库
下载boost库解压后执行编译和install脚本,一般.so或.a自动装在/usr/local/lib下。
int main(){
const size_t n = 100;
int v[n];
// 标准C: 引入一个变量 i, 辅助完成循环访问 v 中每个元素的任务,循环条件上也必须写明循环终止条件,否则容易发生栈溢出错误
size_t i;
for (i = 0; i < n; i++) { v[i] = 0;}
// c++17: 隐藏了迭代的细节(编译器保证正确)
for (auto& x : v) {x = 0;}
}
std::pair<int, int> divide(int dividend, int divisor) {
int quotient = dividend / divisor; // 除数
int remainder = dividend % divisor; // 余数
return std::make_pair(quotient, remainder);
}
int main() {
std::pair<int, int> result = divide(10, 3);
std::cout << "Quotient: " << result.first << ", Remainder: " << result.second << std::endl; // Quotient: 3, Remainder: 1
return 0;
}
int main() {
std::string str1 = "Hello";
std::string str2 = std::move(str1);
std::cout << "str1: " << str1 << std::endl; // 此时 str1 的内容已经被移动,为空
std::cout << "str2: " << str2 << std::endl; // 输出 "Hello"
return 0;
}
#define INFO_LOG Logger<true>(std::cout) // true为false时,日志代码在编译期被完全移除
// Logger 类的构造函数需要一个 std::ostream&(输出流引用)参数,std::cout是标准输出流的全局实例,代表控制台输出
template <bool active>
struct Logger
{
std::ostream& stream;
Logger(std::ostream& _stream) : stream(_stream)
{
if constexpr (active) // true
{
stream << std::dec; // 强制设为十进制格式,C++流对象(如 std::cout)保留格式状态(如十六进制、八进制等)
}
}
template <typename T> // 模板参数支持任意数据类型
Logger& operator<<(const T& value)
{
if constexpr (active)
{
stream << value; // 实际输出
}
return *this;
}
~Logger()
{
if constexpr (active)
{
stream << std::endl; // 输出换行并刷新缓冲区
}
}
};
int main() {
INFO_LOG << "Starting SPD ownership"; // 串口打印
INFO_LOG << std::hex << 255; // 输出 "255" 而不是 "ff"(构造函数中的std::dec会重置格式)
}
int main() {
std::cout << "222" << std::endl;
}
void a()
{
std::cout << "111" << std::endl;
}
void a() __attribute__((constructor)); // main函数之前执行
$ g++ a.cpp -o a
$ ./a
111
222
int main(int argc, char **argv) {
static std::deque<bool>liquidStatus = {true, true, true};
int status = 0; // 如下0 false,1 true
bool nowLiquidStatusFlag = status ? true : false;
std::cout << "nowLiquidStatusFlag is : " << std::boolalpha << nowLiquidStatusFlag << std::endl;
liquidStatus.push_back(nowLiquidStatusFlag);
liquidStatus.pop_front(); // 移除队列最前面的元素
std::cout << "Current liquidStatus: ";
for (bool val : liquidStatus) {
std::cout << val << " "; // 最后空格
}
std::cout << std::endl; // 换行
uint8_t data = 0x1c;
if (data & 0x10) // bit4=1,不用>>4,非0进入
{
std::cout << "val非0" << std::endl;
}
return 0;
}

std::string text = "ID: 123"; // 待匹配的字符串
std::regex pattern(R"(ID:\s(\d+))"); // 定义正则表达式
// \s 匹配 任意空白字符(如空格 、制表符 \t、换行符 \n 等)
// () 表示 捕获组,匹配的内容会被单独存储,可以通过 match[1] 访问
std::smatch match; // 存储匹配结果
if (std::regex_search(text, match, pattern)) { // 执行匹配
std::cout << "整个匹配: " << match[0] << std::endl; // "ID: 123"
std::cout << "捕获组 1: " << match[1] << std::endl; // "123" // 等于std::string indexStr = *(match.begin() + 1);
} else {
std::cout << "未匹配" << std::endl;
}
// std::string_view对字符串的处理,不用把字符串拷贝,直接赋值就行
// 调用dbus获取property时是字符串类型还是uint8/16等都可以用std::any或std::variant,不会崩溃
8.1 boost.asio实现异步inotify事件监听
// a.cpp
#include <fcntl.h>
#include <sys/inotify.h>
#include <boost/asio.hpp>
#include <functional>
#include <iostream>
#include <memory>
static constexpr const size_t eventBufSize = 1 * (sizeof(struct inotify_event) + NAME_MAX + 1); // 选择一个最小的缓冲区大小,恰好装下一个满事件。
static void onEventReceived(struct inotify_event* event) // 处理单个事件
{
std::string_view fileName(event->name);
std::cout << fileName << std::endl;
}
static void rawEventHandler( // async_read_some() 的原始 handler ,需要处理一次读到多个事件的情况,并安排“异步递归”
std::shared_ptr<boost::asio::posix::stream_descriptor> stream, char* buf,
const boost::system::error_code& ec, size_t sizeRead)
{
if (ec)
{
std::cerr << "Failed when reading inotify event " << ec.message()
<< std::endl;
return;
}
char* pos = buf; // 缓冲区中可能存在多个事件,逐一对其进行处理
while (pos - buf < sizeRead)
{
struct inotify_event* event = reinterpret_cast<struct inotify_event*>(pos); // 取出单个事件event
onEventReceived(event);
pos += sizeof(struct inotify_event) + event->len; // inotify_event 是个可变长度结构体,必须加上 len 才能得到其实际长度
}
// 本次读取完成,准备下次读取
stream->async_read_some(boost::asio::buffer(buf, eventBufSize),
std::bind_front(rawEventHandler, stream, buf));
}
static void setupInotify(boost::asio::io_context& io)
{
static char buf[eventBufSize]; // inotify 事件是变长结构体 inotify_event,需要一块连续且长期有效的缓冲区。写成 static → 放在静态存储区,直到进程退出才释放;
int fd = inotify_init1(IN_NONBLOCK); // 创建一个inotify fd , IN_NONBLOCK保证后续read()不会阻塞线程
if (fd < 0)
{
std::cerr << "Unable to set up inotify" << std::endl;
return;
}
inotify_add_watch(fd, "/home/bak/test", IN_CREATE); // 把目录 /home/bak/test 挂到 fd 上,只监听 新建文件 事件
// 如下把裸 fd 包装成 asio 的 AsyncReadStream 对象。
// 必须用 shared_ptr:
// async_read_some 是异步操作,回调可能在 setupInotify 返回很久之后才执行;
// 若 stream 是局部对象,离开作用域就会被析构,fd 被 close(),后续回调里访问已关闭的fd。
// shared_ptr 把生存期塞进回调闭包里,保证“只要回调活着,stream 就活着”。
auto stream = std::make_shared<boost::asio::posix::stream_descriptor>(io, fd);
stream->async_read_some(boost::asio::buffer(buf, eventBufSize), // async_read_some 需要固定大小的缓冲区,因此选择拿 buffer() 把上面的 buf 包一下
std::bind_front(rawEventHandler, stream, buf)); // stream和buf作为rawEventHandler参数,读取 事件即struct inotify_event,将事件存入 buf 中,调用rawEventHandler依次处理buf里的事件
}
int main()
{
// boost.asio异步单线程:替换轮询while(1) 和for循环
// io_context 是一个epoll包装,参考【notes8】,fd有数据调回调
boost::asio::io_context io;
setupInotify(io);
io.run(); // 理论上永不退出
}
// g++ -std=c++20 a.cpp
// ./a.out 一直卡在这里不退出
// /home/bak/test里新增文件,上一行就会打印出文件名
9.raii
作用域(scope):{} 围起来的那一块代码区域。生命周期(lifetime):对象在内存里真正存活的时间段。RAII全称 “Resource Acquisition is Initialization”即资源获取即初始化:将资源的生命周期与对象的生命周期绑定,当对象创建时获取资源,对象销毁时自动释放资源。
class MyClass {
public:
MyClass() { std::cout << "constructor" << std::endl; }
~MyClass() { std::cout << "destructor" << std::endl; }
};
void func()
{
MyClass c;
std::cout << "111" << std::endl;
} // 调用析构
int main()
{
std::cout << "before func()" << std::endl;
func();
std::cout << "after func()" << std::endl;
}
// 运行结果:
before func()
constructor // 调用func() 构造MyClass
111
destructor // MyClass的作用域结束,析构
after func()
struct CustomFd
{
public:
CustomFd() = delete; // 把“默认无参构造函数”删了
...
explicit CustomFd(int fd) : fd(fd) {} // 构造函数
~CustomFd()
{
if (fd >= 0)
{
close(fd);
}
}
};
CustomMap Signature::mapFile(const fs::path& path, size_t size)
{
CustomFd fd(open(path.c_str(), O_RDONLY));
return CustomMap(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd(), 0), size);
} // fd作为CustomFd实例,在此处超出作用域后,会自动调用析构函数,关闭文件描述符,不会导致文件描述符泄漏。struct RemovablePath析构函数中直接删除tar包,避免空间浪费。
void context::spawn_complete() {
{
std::lock_guard l{lock};
...
} // lock_guard和需要保护的变量放置到同一个作用域(用花括号括起来),lock_guard构造时上锁; 超出作用域后释放锁
}
10.智能指针

void foo(char *ptr){ // 我们往往会陷入此场景下需不需要调用 free 释放内存,若释放了会不会dobule free的焦虑。克服这种焦虑的方法只能是:API文档说明或者代码中添加注释
do(ptr); // (1)
free(ptr) // (2)
}
void bar(){
char *p = malloc(100);
timer(5, foo, p); // 立即返回,5s后执行foo(p)
free(p); // (3) // (1)可能遇到悬空指针,正确的做法应从(2) 中执行free,资源所有权的转移,使其看上去违背了“谁申请,谁释放”的原则
}
void foo(int *v){
if( v ! = NULL) // 对指针进行了判空处理
*v = 10; // 存在 use after free 的可能性,不为空不代表没被释放
free(v) // 释放指针指向的内存:存在double free的可能性。若不调用free,存在内存泄漏的可能性
}
// 如下cpp智能指针解决下面问题
void foo(std::unique_ptr<int> v) // 函数参数是一个独占式智能指针,foo管理其生命周期
{
if(v) { // 指针判空
*v = 10; // 赋值,因是独占所有权,指针一定有效
}
} // 指针变量的生命周期结束,自动调用析构函数释放该指针。自动释放,不存在内存泄漏问题。指针别处不可见,不会use after free
如下是怎么正确使用 unique_ptr:
// 值传递 = 所有权交割,必须 std::move :
void foo(std::unique_ptr<MyClass> p){(void)p;}
int main(){
auto p = std::make_unique<MyClass>(); // 同std::uniqpue_ptr<MyClass> p{new MyClass()}
foo(p); // 编译报错,没有显式的资源转移表达
foo(std::move(p)); // 显式表达资源所有权转移,资源转移后p失效,编译通过
}
// 引用传递 = 只给钥匙不改户主,reset 可以偷偷把房子拆了再盖新的,但房产证上还是原来那个人:
void foo(std::unique_ptr<MyClass>& p){
p.reset(new MyClass); // 重装指针,使unique_ptr释放原来管理的指针,进而管理一个新指针
}
int main(){
MyClass* ptr = new MyClass;
std::cout << ptr << std::endl; // 0x5561a66b62b0
std::unique_ptr<MyClass> p { ptr }; // 让unique_ptr管理原始指针
std::cout << p.get() << std::endl; // 0x5561a66b62b0 打印出 unique_ptr 管理的原始指针,就是ptr
foo(p);
std::cout << p.get() << std::endl; // 0x5561a66b66e0 打印出 unique_ptr 管理的原始指针,因为发生了重装则不再是ptr
}
std::unique_ptr<int> pr1(new int(10));
std::unique_ptr<double> pr2 = std::make_unique<double>(2.0);
struct MyClass {
MyClass(int val) : value(val){}
int value;
};
std::unique_ptr<MyClass> ptr3 = std::make_unique<MyClass>(30);
std::cout << *ptr1 << std::endl; // 打印 10
std::cout << ptr3->value << std::endl; // 打印 30
(*ptr3).value = 50;
std::cout << ptr3->value << std::endl; // 打印 50
10.1 从0实现unique_ptr
struct Student{int age; std::string name;};
void foo(){
Student *s = new Student(18, "Jim");
doSomeThings(s);
delete s;
}
class StudentWrapper{
public:
explicit StudentWrapper(Student *ptr = nullptr): ptr_(ptr){}
~StudentWrapper(){delete ptr_;}
Student* get() const {return ptr_;}
private:
Student *ptr_;
};
void foo(){
StudentWrapper s(new Student(18, "Jim"));
doSomeThing(s.get());
} // RAII
template <typename T>
class UniquePtr {
public:
explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {} // : ptr_(ptr) 即 把形参ptr值直接赋给数据成员ptr_
~UniquePtr(){delete ptr_;}
T* get() const {return ptr_;}
T& operator*() const {return *ptr_;}
T* operator->() const {return ptr_;}
private:
T* ptr_;
};
UniquePtr<Student> ptr1 { new Student(18, "Jim") };
UniquePtr<int> ptr2 { new int(10) };
std::cout << ptr1->name << std::endl;
std::cout << *ptr2 << std::endl;
// 再添加 禁掉拷贝构造和赋值行为,表明独占所有权
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
// 实现一个make_unique函数
template <typename T, typename... Args>
UniquePtr<T> make_unique(Args&&... args){return UniquePtr<T>(new T(std::forward<Args>(args)...));}
11.引用
int i ; // 值类型,有地址,可以&i取地址
int* o; // 指针类型
int& ii=i; // 引用类型,必须赋一个值,指向引用的谁
11.1 左右值
cpp将变量分为左值(有地址)和右值(无地址)。
11.2 为什么要右值引用

如下接着上面,隐含buffer b=buf,调用打印“拷贝构造”函数即值传递,拷贝构造函数里最后2行开和拷贝内存里数值到当前buf,有开销。为避免开销,只改void dothing(buffer& b),变成引用/左值传递即传地址,运行没有任何打印。
如下传入是临时构造的即右值,void dothing(const buffer& b)也可以(表示这个引用不会被修改即const ,因为右值不能被修改)。

当传入右值是:dothing(拷贝构造),右值是将亡值(经历析构),不需要构造里开和拷贝内存,所以添加右值引用构造器即move拷贝构造器。
11.3 copy & swap
如下移动构造里进行swap相当于拷贝将亡值到this。
11.4 std::move

remove_reference字面意思去引用,简化如下:
12.完美转发
传入左值或右值在函数体里也是左值或右值,不变。
如下在main中,1是右值,却调用了左值构造器。

12.1 引用折叠
如下只有最后一行转换后是右值。
12.2 万能引用
如下不管传入右/左值,都打印万能引用。
12.3 std::forward
如下满足了完美转发:


如下是std::forward源码:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)