-
今天是远程办公的第4天,渐渐的找到了一点在家办公的节奏了,改了几个shell脚本,在改动过程中,发现还有一些知识点需要巩固,这里写出来,加深下印象。 关于$符号,大家肯定都不陌生,在shell脚本中,$的作用还是比较大多的,这里将所有的$组合都列出来,大家可以看下效果。 $组成的命令大概有下面几个:$0,$1...$?,$!,$$,$*,$#,$@下面分别看看这些意思是什么:1.$0,$1$0和$1比较好理解,他们的意思,分别是脚本名称和脚本的第一个参数,我们举个例子来看:123456789jwfyyzdembp:tmp jwfyyz$ cat test.shecho '$0的含义是':$0echo '$1的含义是':$1jwfyyzdembp:tmp jwfyyz$ ./test.sh yeyz$0的含义是:./test.sh$1的含义是:yeyzjwfyyzdembp:tmp jwfyyz$ sh test.sh yeyz$0的含义是:test.sh$1的含义是:yeyz 需要注意一点,当使用./test.sh的方法来运行脚本的时候,$0的值是./test.sh,而不是test.sh2.$?$?指的是最后运行的命令的执行返回码,执行成功,则返回0,否则返回1,举例如下:123456789101112131415jwfyyzdembp:tmp jwfyyz$ cat test.sh echo '$0的含义是':$0echo '$1的含义是':$1ls -l a.txtecho '$?的结果是'$?ls -l test.shecho '$?的结果是'$? jwfyyzdembp:tmp jwfyyz$ sh test.sh yeyz$0的含义是:test.sh$1的含义是:yeyzls: a.txt: No such file or directory$?的结果是1-rwxr-xr-x 1 jwfyyz wheel 124 2 7 20:32 test.sh$?的结果是0 可以看到,第一次运行ls -l a.txt的时候,由于我们的目录中没有a.txt这个文件,所以返回是1,第二次由于test.sh文件是存在的,所以返回的结果是03.$$ $$指的是脚本运行的当前进行id号,举例如下:1234567891011jwfyyzdembp:tmp jwfyyz$ cat test.sh pwdecho $$sleep 10 jwfyyzdembp:tmp jwfyyz$ sh test.sh &[1] 60658 jwfyyzdembp:tmp jwfyyz$ ps -ef|grep test 501 60658 60529 0 8:36下午 ttys000 0:00.01 sh test.sh 501 60661 60529 0 8:36下午 ttys000 0:00.00 grep test 我们在test.sh脚本后面设置了sleep 10的命令,脚本运行完会睡眠10s,脚本中打印出来了值60658,此时我们查看当前脚本的进程号,发现子进程的进程id是606584.$*和$@ 这两个放在一起,主要是因为他们都代表引用的所有参数,单独打印出来结果,都是一样的,但是循环打印他们的内容,则会看出区别,举例如下:1234567891011121314151617181920212223jwfyyzdembp:tmp jwfyyz$ cat test.sh echo '$*的结果是':$*echo '$@的结果是':$@echo "-- \$* 演示 ---"for i in "$*"; do echo $idone echo "-- \$@ 演示 ---"for i in "$@"; do echo $idone jwfyyzdembp:tmp jwfyyz$ sh test.sh yeyz 1 2 3$*的结果是:yeyz 1 2 3$@的结果是:yeyz 1 2 3-- $* 演示 ---yeyz 1 2 3-- $@ 演示 ---yeyz123 可以看到,循环打印的时候,$@ 5.$# $#容易理解,它的意思是参数的个数。举例:123456789jwfyyzdembp:tmp jwfyyz$ cat test.sh echo '$*的结果是':$*echo '$@的结果是':$@echo '$#的结果是':$# jwfyyzdembp:tmp jwfyyz$ sh test.sh yeyz 1 2 3$*的结果是:yeyz 1 2 3$@的结果是:yeyz 1 2 3$#的结果是:46.$! 该命令是显示shell脚本中上一个后台执行命令的进程id号,如下:12345678910111213141516171819jwfyyzdembp:tmp jwfyyz$ cat test.sh echo '$*的结果是':$*echo '$@的结果是':$@echo '$#的结果是':$#sleep 10 &echo '$!的结果是':$!sleep 10 jwfyyzdembp:tmp jwfyyz$ sh test.sh yeyz 1 2 3 &[1] 61237$*的结果是:yeyz 1 2 3$@的结果是:yeyz 1 2 3$#的结果是:4$!的结果是:61238 jwfyyzdembp:tmp jwfyyz$ ps -ef|grep sleep 501 61238 61237 0 9:12下午 ttys000 0:00.00 sleep 10 501 61239 61237 0 9:12下午 ttys000 0:00.00 sleep 10 501 61241 60529 0 9:12下午 ttys000 0:00.00 grep sleep 在test.sh中,我们使用了sleep 10这样的命令在后台执行,然后我们可以看到,$!返回的值是61238,当我们ps -ef命令去查询进程信息的时候,可以看到,61238这个进程对应的命令就是sleep 10,注意和$$区分开来,$$反应的是当前脚本执行的进程号,$!反应的是脚本中某个命令的进程号。
-
代码链接: https://gitee.com/ascend/samples/tree/master/facedetection/for_atlas200dk_1.7x.0.0_c++Atlas 200DK上实现face detection的sample,第一次运行成功,没有任何问题;但是停止之后,想运行第二次,则出现如下报错。查了些资料,说是和进程有关,于是我就执行了ps -ef | grep ada,确实发现了一个进程,kill -9 ID执行完之后再次cd /var, 然后ls,理论上应该这时候var里面只有一个ada,但是我的var里面充满了无数文件。。。。然后我试图通过rm -rf /var/log/*来删除/var/log/npu/slog,但是error提示说cannot remove the file, Device or source is busy。。。不知道怎么解决了,求大佬指导!
-
在学习 Linux 系统权限相关的主题时,我们首先关注的基本都是文件的 ugo 权限。ugo 权限信息是文件的属性,它指明了用户与文件之间的关系。但是真正操作文件的却是进程,也就是说用户所拥有的文件访问权限是通过进程来体现的。本文主要介绍进程的权限,并通过示例解释用户身份与进程权限之间的关系。说明:本文的演示环境为 ubuntu 16.04。基本概念用户对于支持多任务的 Linux 系统来说,用户就是获取资源的凭证。权限权限用来控制用户对计算机资源(CPU、内存、文件等)的访问,一般会分为认证和授权两步。比如用户先经过认证机制(authentication)登录系统,然后由授权系统(authorization)对用户的操作进行授权。进程进程是任何支持多道程序设计的操作系统中的基本概念。通常把进程定义为程序执行时的一个实例。因此,如果有 10 个用户同时运行 vi,就会有 10 个独立的进程(尽管它们共享同一份可执行代码)。实际上,是进程在帮助我们完成各种任务。进程就是用户访问计算机资源的代理,用户执行的操作其实是带有用户身份信息的进程执行的操作。进程权限既然是进程在为用户执行具体的操作,那么当用户要访问系统的资源时就必须给进程赋予权限。也就是说进程必须携带发起这个进程的用户的身份信息才能够进行合法的操作。从登陆过程观察进程携带的用户身份信息在 Linux 系统启动后,init 系统会 fork 出子进程执行 /sbin/getty 程序等待用户登录。当用户进行登录操作时,该子进程通过 exec 函数开始执行 /bin/login 程序(此时该进程已经变成了 login 进程)。由 login 进程验证我们的用户名和密码并查询 /etc/passwd 和 /etc/shadow 确定其合法性。如果是合法的用户,该进程再次通过 exec 函数执行用户的默认 shell 程序,此时的 login 进程就变成了 shell 进程(笔者机器上是 bash 进程)。并且该 shell 进程的有效身份被设置成为该用户的身份,之后 fork 此 shell 进程的子进程都会继承该有效身份。我们可以通过下图来理解用户从 tty 登录系统的过程(此图来自互联网):上图描述了 init 进程、getty 进程、login 进程和 shell 进程的交互。简单点说就是:用户登录后, shell 进程的有效用户就是该用户。下面我们来了解下进程的用户信息。进程的 real user id、effective user id 和 saved set user id通过 cat /proc/<PID>status 命令,我们可以查看到进程所属的用户和组相关的信息:通过 man proc 可以查询到第一行的四个数字分别是 real user id, effective user id, saved set user id 和 filesystem UID,第二行则是对应的组 ID。这里我们只介绍第一行中的前三个 ID,即 real user id, effective user id 和 saved set user id。real user idreal user id 是执行进程者的 user id,一般情况下就是用户登录时的 user id。子进程的 real user id 从父进继承。通常这个是不更改的,也不需要更改。比如我以用户 nick 登录 Linux 系统,我接下来运行的所有命令的进程的 real user id 都是 nick 的 user id。effective user id如果要判断一个进程是否对某个文件有操作权限,验证的是进程的 effective user id,而不是 real user id。通常我们是不建议直接使用 root 用户进行操作的,但是在很多情况下,程序可能需要特殊的权限。比如 passwd 程序需要 root 权限才能够为普通用户修改密码,一些 services 程序的操作也经常需要特殊的权限。为此,Linux 中设计了一些特殊的权限,请参考《Linux 特殊权限 SUID,SGID,SBIT》一文。这里我们以 passwd 程序为例,为二进制可执行文件 /usr/bin/passwd 设置 set-user-id bit=ON,这个可执行文件被用 exec 启动之后的进程的 effective user id 就是这个可执行文件的 owner id,而并非父进程的 real user id。如果 set-user-id bit=OFF 的时候,这个被 exec 起来的进程的 effective user id 应该是等于进程的 user id 的。所以,effective user id 存在的意义在于,它可能和 real user id 不同。saved set user idsaved set user id 相当于是一个 buffer,在 exec 函数启动之后,它会拷贝 effective user id 位的信息覆盖自己。对于非 root 用户来说,可以在未来使用 setuid() 函数将 effective user id 设置成为 real user id 或 saved set user id 中的任何一个。但是不允许非 root 用户用 setuid() 函数把 effective user id 设置成为任何第三个 user id。对于 root 用户来说,调用 setuid() 的时候,将会设置所有的这三个 user id。从总体上来看,进程中 real user id, effective user id 和 saved set user id 的设计是为了让 unprivilege user 可以获得两种不同的权限。同时我们也可以得出下面的结论:Linux 系统通过进程的有效用户 ID(effective user id) 和有效用户组 ID(effective group id) 来决定进程对系统资源的访问权限。其实我们通过 ps aux 查看的结果中,第一列显示的就是进程的 effective user:Shell 中外部命令的执行方式在 shell 中执行的命令分为内部命令和外部命令两种。内部命令:内建的,相当于 shell 的子函数外部命令:在文件系统的某个路径下的一个可执行文件外部命令的执行过程如下:Shell 通过 fork() 函数建立一个新的子进程,新的子进程为当前 shell 进程的一个副本。在新的进程里,从 PATH 变量所列出的目录中寻找指定的命令程序。当命令名称包含有斜杠(/)符号时,将略过路径查找步骤。在新的进程里,通过 exec 系列函数,以所找到的新程序替换 shell 程序并执行。子进程退出后,最初的 shell 会接着从终端读取并执行下一条命令。我们通过下面的例子来理解在 shell 中执行外部命令的过程,例子很简单就是通过 cat 命令查看一个文本文件 test.log:$ cat test.log我们先来检查一下当前用户以及相关文件的权限:当前用户 nick 的 real user id 为 1000,/bin/cat 文件的所有者为 root,但是所有人都有执行权限,test.log 文件的所有者为 nick。我们结合下图来介绍 cat test.log 命令的执行过程:当我们在 shell 中执行一个外部程序的时候,默认情况下进程的 effective user ID 等于 real user ID,进程的 effective group ID 等于 real group ID(接下来的介绍中省略 group ID)。当我们以用户 nick 登录系统,并在 bash 中键入 cat test.log 命令并回车后。Bash 先通过 fork() 建立一个新的子进程,这个新的子进程是当前 bash 进程的一个副本。新的进程在 PATH 变量指定的路径中搜索 cat 程序,找到 /bin/cat 程序后检查其权限。/bin/cat 程序的所有者为 root,但是其他人具有读和执行的权限,所以新进程可以通过 exec 函数用 cat 程序的代码段替换当前进程中的代码段(把 /bin/cat 程序加载到了内存中,此时的进程已经变成了 cat 进程,cat 进程会从 _start 函数开始执行)。由于 cat 进程是由用户 nick 启动的,所以 cat 进程的 effective user ID 是 1000(nick)。同时 cat 进程的 effective user ID 和 test.log 文件的 owner ID 相同(都是 1000),所以 cat 进程拥有对此文件的 rw- 权限,那么顺理成章地就可以读写 test.log 文件的内容了。下面我们演示一个通过设置特殊权限 set uid ID 改变进程 effective user ID 的例子。创建文件 root.log,权限为 640,此时只有 root 有权限读写该文件的内容,用户 nick 连读取该文件的权限都没有:然后通过设置特殊权限 set uid ID 让运行 cat 程序的进程具有 root 权限:$ sudo chmod 4755 /bin/cat 现在可以了!因为运行 cat 程序的进程的 effective user ID 变成了 root。记得要把 /bin/cat 的权限改回去呀:$ sudo chmod 755 /bin/catShell 脚本的执行方式在 shell 中执行脚本的方式和执行外部命令的方式差不多,比如我们要执行下面的脚本:$ /bin/bash ./test.sh这时同样会 fork 出一个子进程。只不过脚本与程序相比没有代码段,也没有 _start 函数,此时 exec 函数就会执行另外一套机制。比如我们在 test.sh 文件的第一行通过 #!/bin/bash 指定了一个解释器,那么解释器程序的代码段会用来替换当前进程的代码段,并且从解释器的 _start 函数开始执行,而这个文本文件被当作命令行参数传给解释器。所以上面的命令执行过程为:Bash 进程 fork/exec 一个子 bash 进程用于执行脚本,子 bash 进程继承父进程的环境变量、用户信息等内容,父进程等待子 bash 进程终止。总结文件上的权限信息和用户的信息都是静态的,而进程是动态的,它把自身携带的用户信息和将要进行的操作结合起来,从而实现权限管理。至此我们也基本上搞明白了 Linux 权限系统的工作原理。转自:https://www.cnblogs.com/sparkdev/p/9694103.html
-
top 命令主要用于查看进程的相关信息,同时它也会提供系统平均负载,cpu 信息和内存信息。下面的截图展示了 top 命令默认提供的信息:系统平均负载top 命令输出中的第一行是系统的平均负载,这和 uptime 命令的输出是一样的:13:05:49 表示系统当前时间。up 7 days 表示系统最后一次启动后总的运行时间。1 user 表示当前系统中只有一个登录用户。load average: 0.01, 0.04, 0.00 表示系统的平均负载,最后的三个数字分别表示最后一分钟的系统平均负载,最后五分钟的系统平均负载,最后十五分钟的系统平均负载。任务信息汇总在 linux 系统中,一般把进程和线程统称为任务。第二行信息是对当前系统中所有任务的统计:Tasks:270 total 表示当前系统的进程总数。1 running 表示当前系统中有 1 个正在运行的进程。269 sleeping 表示当前系统中有 269 个休眠的进程。0 stopped 表示停止状态的进程数为 0。0 zombie 表示处于僵死状态的进程数为 0。CPU 信息第三行显示 CPU 的使用情况:这里一共有八个字段,是我们了解 CPU 负载的主要依据,下面我们逐一介绍。us进程在用户地址空间中消耗 CPU 时间的百分比。像 shell程序、各种语言的编译器、数据库应用、web 服务器和各种桌面应用都算是运行在用户地址空间的进程。这些程序如果不是处于 idle 状态,那么绝大多数的 CPU 时间都是运行在用户态。sy进程在内核地址空间中消耗 CPU 时间的百分比。所有进程要使用的系统资源都是由 Linux 内核处理的。当处于用户态(用户地址空间)的进程需要使用系统的资源时,比如需要分配一些内存、或是执行 IO 操作、再或者是去创建一个子进程,此时就会进入内核态(内核地址空间)运行。事实上,决定进程在下一时刻是否会被运行的进程调度程序就运行在内核态。对于操作系统的设计来说,消耗在内核态的时间应该是越少越好。在实践中有一类典型的情况会使 sy 变大,那就是大量的 IO 操作,因此在调查 IO 相关的问题时需要着重关注它。nini 是 nice 的缩写,可以通过 nice 值调整进程用户态的优先级。这里显示的 ni 表示调整过 nice 值的进程消耗掉的 CPU 时间。如果系统中没有进程被调整过 nice 值,那么 ni 就显示为 0。idCPU 处于 idle 状态的百分比。一般情况下, us + ni + id 应该接近 100%。waCPU 等待磁盘 IO 操作的时间。和 CPU 的处理速度相比,磁盘 IO 操作是非常慢的。有很多这样的操作,比如:CPU 在启动一个磁盘读写操作后,需要等待磁盘读写操作的结果。在磁盘读写操作完成前,CPU 只能处于空闲状态。Linux 系统在计算系统平均负载时会把 CPU 等待 IO 操作的时间也计算进去,所以在我们看到系统平均负载过高时,可以通过 wa 来判断系统的性能瓶颈是不是过多的 IO 操作造成的。hi & si这两个值表示系统处理中断消耗的时间。中断分为硬中断和软中断,hi 表示处理硬中断消耗的时间,si 表示处理软中断消耗的时间。硬中断是硬盘、网卡等硬件设备发送给 CPU 的中断消息,当 CPU 收到中断消息后需要进行适当的处理(消耗 CPU 时间)。软中断是由程序发出的中断,最终也会执行相应的处理程序(消耗 CPU 时间)。st只有 Linux 在作为虚拟机运行时 st 才是有意义的。它表示虚机等待 CPU 资源的时间(虚机分到的是虚拟 CPU,当需要真实的 CPU 时,可能真实的 CPU 正在运行其它虚机的任务,所以需要等待)。小写字母 t 可以控制是否显示任务信息汇总和 CPU 信息。没错,它能控制是否显示两行信息。内存信息内存信息包含两行内容,内存和交换空间:top 命令中这部分的输出和 free 命令的输出基本相同,笔者在《linux free 命令》一文已经详细介绍过,这里不再赘述。控制显示单位top 命令默认以 K 为单位显示内存大小,这让人十分抓狂。好在我们可以通过大写字母 E 来切换内存信息区域的显示单位(注意,E 不能控制任务区域中的内存单位),下图以 GB 显示内存大小:小写字母 m 可以控制是否显示内存信息。任务详情内存信息下面是一个空行(其实是与用户交互的区域),空行的下面就是任务详情区域:默认情况下这里会显示 12 列数据,都是我们比较关心的进行相关的信息,下面我们一个一个的介绍。PID 表示进程 ID。USER 表示进程所有者的有效用户名称。简单说就是以哪个用户权限启动的进程。比如上图中有两个进程是用户 nick 启动的,还有一个是用户 prometheus 启动的,其它都是 root 用户启动的。PR 表示进程执行的优先级,PR 的值是以 Linux 内核的视角看到的进程执行的优先级。NI 从用户视角看到的进程执行优先级。注意上图中 NI 值为 -20 的两个进程,它们的 PR 值都是 0。VIRT 表示进程使用的虚拟内存大小。RES 表示进程使用的物理内存大小。SHR 表示进程使用的共享内存的大小。S 表示进程当前的状态。S 值有下面几种: D 不可中断的睡眠状态(uninterruptible sleep) I idle 状态 R 进程在 running 队列中,正在运行或准备运行(running) S 睡眠状态(sleeping) T 停止状态(stopped by job control signal) t 跟踪状态(stopped by debugger during trace) Z 僵尸状态(zombie)%CPU 表示进程使用 CPU 的百分比。%MEM 表示进程使用内存的百分比。TIME+ 表示进程累计使用的 CPU 时间。COMMAND 表示运行进程对应的程序。一般情况下这些信息足够了,但是如果你还想要更多的信息,你可以尝试添加更多的列。按下小写字母 f 可以进入任务信息的配置界面:在这里你可以选择要显示的列,并且可以配置以哪一列进行排序。显示内存大小的单位问题在任务详情区域也同样存在,默认的单位也是 KB。要改变它的单位需要使用小写字母 e 来进行切换,比如我可以把它切换为以 MB 为单位:这样看起来就直观多了!top 是一个非常复杂的命令,上面介绍的内容仅仅是一些皮毛而已。即便如此,你也可以用它来干不少的事情了!如果你想了解更多详细的信息,请参考 top 的使用手册。我们接下来介绍一些常见的用例。显示多个 CPU 核心的详细信息无论系统中有多少个 CPU 核心,默认的 CPU 信息总是输出一行,即所有核心加起来的综合数据。能不能查看各个 CPU 核心单独的数据呢?答案是,可以的。按键盘上的数字 1 就可以在不同的视图之间切换了:以某列对进程排序按小写字母 f 进入排序设置界面,选择某一列,按小写 's' 指定排序,然后退出。奇怪的是默认主界面上并看不出是以哪列排序的!可以使用小写字母 x 来粗体显示当前排序的列:可以看到 %CPU 列的字体加粗了吗?虽然不太明显,但勉强可以看到了。在使用 x 以粗体显示排序的列后,还可以使用 b 来高亮显示排序的列(注意,b 是配合 x 使用的):还有一些预定义的命令可以直接完成以某列排序的功能,比如大写字母 M 以 %MEM 列排序;大写字母 N 以 PID 列排序;大写字母 P 以 %CPU 列排序;大写字母 T 以 TIME+ 列排序。M %MEM N PID P %CPU T TIME+ 反转排序的结果是常见的需求,大写字母 R 可以将当期排序的结果反转。显示进程执行的完整命令默认 COMMAND 列只显示程序的名字,并不包含程序的路径。有时能够看到程序的完整路径是很方便的。你可以通过小写字母 c 来切换 COMMAND 列的显示模式:不仅是程序的完整路径,连启动程序的参数都显示出来了!隐藏 idle 的进程在我们调查问题时,总希望以最快的方式找到繁忙的进程。但是 top 命令会把所有的进程列出,这就需要我们通过昏花的老眼去扫描一行行的进程信息。还好,我们可以借助小写字母 i 来控制是否显示处于 idle 状态的进程!使用这个命令后你会发现世界好清爽啊!只显示某个用户的进程如果你想查看以某个用户权限启动的进程,可以使用小写字母 u 。这会提示你输入用户的名称,在你输入用户名称后,按回车键:上图中笔者输入的用户名为 nick,按回车键后就会过滤出所有以用户 nick 权限启动的进程。 过滤进程列表可以根据进程名称、CPU 或 内存使用情况等指标过滤进程列表。当然,也可以同时应用多个过滤条件。常用的几个快捷键为:o -> 提示用户输入过滤条件enter -> 应用过滤条件ctrl + o -> 显示当前的过滤条件= -> 清除所有的过滤条件按下字母 o 后,top 命令提示用户输入过滤过滤条件:此时我们可以输入过滤条件并按回车键,比如输入下面的命令:COMMAND=top过滤的结果是进程列表中只显示 top 进程的信息。注意,这里的 COMMAND 要大写。还可以过滤 CPU 占用率超过某个值的进程(注意,要带上小数位):%CPU>5.0按下 ctrl + o 可以查看当前的过滤条件:可以连续添加多个过滤条件,这些过滤条件会同时起作用。按下 = 键则会清除所有的过滤条件。top 命令的配置文件top 命令是有配置文件的,也就是说你通过命令修改的配置都可以保存下来。保存配置的命令为大写字母 W。在你修改了 top 命令的配置后按下大写字母 W,然后退出 top 命令并再次执行 top 命令,此时你的修改仍然在起作用。转自:https://www.cnblogs.com/sparkdev/p/8176778.html
-
一、glibc aio1、名称由于是glibc提供的aio函数库,所以称为glibc aio。glibc是GNU发布的libc库,即c运行库。另外网上还有其他叫法posix aio,都是指glibc提供的这套aio实现方案。2、主要接口glibc aio主要包含如下接口:函数功能int aio_read(struct aiocb *aiocbp);提交一个异步读int aio_write(struct aiocb *aiocbp);提交一个异步写int aio_cancel(int fildes, struct aiocb *aiocbp);取消一个异步请求(或基于一个fd的所有异步请求,aiocbp==NULL)int aio_error(const struct aiocb *aiocbp);查看一个异步请求的状态(进行中EINPROGRESS?还是已经结束或出错?)ssize_t aio_return(struct aiocb *aiocbp);查看一个异步请求的返回值(跟同步读写定义的一样)int aio_suspend(const struct aiocb * const list[], int nent, const struct timespec *timeout);阻塞等待请求完成glibc aio提供函数API,是比较通俗易懂的。3、实现原理在glibc aio的实现原理是,用多线程同步来模拟异步IO。实际上,为了避免线程的频繁创建、销毁,当有多个请求时,glibc aio会使用线程池,但以上原理是不会变的,尤其要注意的是:我们的回调函数是在一个单独线程中执行的。缺点:glibc aio 广受非议,存在一些难以忍受的缺陷和bug,饱受诟病,是极不推荐使用的。详见:http://davmac.org/davpage/linux/async-io.html二、libaio1、名称libaio是由linux内核提供的aio实现方案,类似于windows api。由于是linux kernel提供的api,故也叫linux kernel aio,或者原生aio,native aio。由于linux下aio实现方式较多,网上叫法很乱,所以这里特意总结下,方便大家区分。2、主要接口它主要包含如下系统调用接口:函数功能int io_setup(int maxevents, io_context_t *ctxp);创建一个异步IO上下文(io_context_t是一个句柄)int io_destroy(io_context_t ctx);销毁一个异步IO上下文(如果有正在进行的异步IO,取消并等待它们完成)long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);提交异步IO请求long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);取消一个异步IO请求long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout)等待并获取异步IO请求的事件(也就是异步请求的处理结果)其中,struct iocb主要包含以下字段:struct iocb { void *data; /* Return in the io completion event */ unsigned key; /*r use in identifying io requests */ short aio_lio_opcode; short aio_reqprio; int aio_fildes; union { struct io_iocb_common c; struct io_iocb_vector v; struct io_iocb_poll poll; struct io_iocb_sockaddr saddr; } u;};struct io_iocb_common { void *buf; unsigned long nbytes; long long offset; unsigned flags; unsigned resfd;};iocb是提交IO任务时用到的,可以完整地描述一个IO请求:data是留给用来自定义的指针:可以设置为IO完成后的callback函数;aio_lio_opcode表示操作的类型:IO_CMD_PWRITE | IO_CMD_PREAD;aio_fildes是要操作的文件:fd;io_iocb_common中的buf, nbytes, offset分别记录的IO请求的mem buffer,大小和偏移。struct io_event { void *data; struct iocb *obj; unsigned long res; unsigned long res2;};io_event是用来描述返回结果的:obj就是之前提交IO任务时的iocb;res和res2来表示IO任务完成的状态。3、实现原理libaio与Glibc的多线程模拟不同 ,它是真正做到内核的异步通知,是真正意义上的异步IO。听起来Kernel Native AIO几乎提供了近乎完美的异步方式,但如果你对它抱有太高期望的话,你会再一次感到失望。使用限制:目前libaio仅支持O_DIRECT标志,即仅支持Direct I/O。Direct I/O可以简单理解为直接读写IO,读写期间无缓存。Linux中直接I/O机制介绍:https://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html缺点:目前libaio仅支持Direct I/O方式来对磁盘读写,这意味着,你无法利用系统的缓存,同时它要求读写的的大小和偏移要以区块的方式对齐。三、libeio在当年,linux下已有的AIO (异步IO)解决方案:Glibc的AIO,在用户态,多线程同步来模拟的异步IO;libaio,需要linux内核2.6.22以上,且仅支持Direct I/O。但两者都存在让使用者望而却步的问题:Glibc的AIO bug太多,而且IO发起者并不是最后的IO终结者(callbak是在单独的线程执行的);libaio只支持O_DIRECT方式,无法利用Page cache。正是由于上述原因,Marc Alexander Lehmann大佬决定自己开发一个AIO库,即libeio。libeio也是在用户态用多线程同步来模拟异步IO,但实现更高效,代码也更可靠,目前虽然是beta版,但已经可以上生产了(node.js底层就是用libev和libeio来驱动的)。还要强调点:libeio里IO的终结者正是当初IO的发起者(这一点非常重要,因为IO都是由用户的request而发起,而IO完成后返回给用户的response也能在处理request的线程中完成)。libeio提供全套异步文件操作的接口,让使用者能写出完全非阻塞的程序。缺点:严格来讲,libeio也不属于真正的异步IO,仍然是通过用户态多线程来模拟的,性能上与真正的异步IO有差距。github地址:https://github.com/kindy/libeio代码量不大,几千行,感兴趣可以研究下。四、io_uring在过去的数年间,针对上述缺陷,限制的很多改进努力都未果,如Glibc AIO、libaio、libeio。虽然在使用和性能上提升了很多,但是,在Linux 上,依然没有比较完美的异步文件IO方案。直到,Linux 5.1合入了一个新的异步IO框架和实现:io_uring,由block IO大神Jens Axboe开发。这对当前异步IO领域无疑是一个喜大普奔的消息,这意味着,libaio的时代即将成为过去,io_uring的时代即将开启。为了方便使用,Jens Axboe还开发了一套liburing库,同时在fio中提供了ioengine=io_uring的支持。通过liburing库,应用不必了解诸多io_uring的细节就可以简单地使用起来。例如,无需担心memory barrier,或者是ring buffer管理之类等。一句话总结 io_uring 就是:一套全新的 syscall,一套全新的 async API,更高的性能,更好的兼容性,来迎接高 IOPS,高吞吐量的未来。这个特性,才出来,需要5.1以上内核才能支持,具体好不好用,后续才知道,现在似乎搜索到的内容较少。io_uring使用参考:《原生的 Linux 异步文件操作,io_uring 尝鲜体验》《Linux 5.1内核AIO 的新归宿:io_uring》五、总结linux异步IO实际上是利用了CPU和IO设备可以异步工作的特性(IO请求提交的过程主要还是在调用者线程上同步完成的,请求提交后由于CPU与IO设备可以并行工作,所以调用流程可以返回,调用者可以继续做其他事情)。linux的异步IO发展之路,还是比较曲折的,没有一个完美的实现。不像windows下异步IO,IOCP就是标杆,各种吊打。在目前情况下,libeio就是比较不错的方案了。但是如果你的程序中只用到Direct I/O,那么推荐使用libaio。在将来,随着Linux 5.1以上版本的更新,如果io_uring给力的话,linux异步IO很可能就一统天下了。转自:https://blog.csdn.net/zyhse/article/details/109188990?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160465159519195264759702%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=160465159519195264759702&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v28-19-109188990.first_rank_ecpm_v3_pc_rank_v2&utm_term=linux&spm=1018.2118.3001.4449
-
调优介绍使用系统性能分析工具对目标环境的空载系统执行系统性能全景分析、进程线程分析,找到性能瓶颈点,并根据分析结果进行优化修改,从而实现系统的性能增强或降低系统资源的消耗。组网环境说明:本实践以TaiShan 200服务器(型号2280)+CentOS7.6组网举例,Hyper Tuner在其他鲲鹏平台和操作系统上的操作类似。表格1 测试环境项目说明服务器TaiShan 200 服务器(型号2280)CPUKunpeng 920 4826OSCentOS 7.6应用系统空载情况下,不运行应用性能分析工具Hyper Tuner2.2.T2.SPC100前提条件1. 服务器和操作系统正常运行。2. PC端已经安装SSH远程登录工具。3. 目标环境上HyperTuner工具已经安装完成,并正常运行。调优思路1. 在进行调优之前,先用HyperTuner工具对目标环境的空载系统进行全局的系统性能分析。2. 对全景性能分析中,有异常的指标进一步分析,并根据优化建议进行优化修改。3. 对优化后的系统再次进行全景性能分析,验证调优后的效果。操作步骤系统全景分析执行性能全景分析1. 登录系统性能调优工具。2. 创建工程。3. 创建全景分析任务,配置参数如下图所示。4. 任务分析成功后,在分析结果的“性能”页签中用列表方式查看CPU的利用率信息。5. 依次对%usr(用户态CPU利用率)、%sys(内核态CPU利用率)、%soft(软中断CPU利用率)等指标进行排序,找出控制状态下,CPU利用率较高的指标。经过排序发现,在128核的空载系统上,有12个核的软中断占用率均达到96%以上,整体占128核的9.27%,说明该空载系统上存在软中断的性能问题。性能瓶颈分析在性能全景分析的结果中,发现有12个核的软中断占用率在96%以上,整体占CPU(128核)利用率的9.27%,但目前系统是空载状态,没有运行其他应用,所以系统存在软中断的性能问题。但目前仅仅通过全景分析,定位不到具体是哪一个进程发生了软中断,定位不到性能的瓶颈点。所以需要进步执行进程线程分析任务,定位出具体是那些进行或线程发生了软中断。执行进程线程分析1. 创建进程线程分析任务,配置参数如下所示。2. 任务分析成功后,查看分析结果。 从分析结果中可以看到有12个ksoftirqd进程的CPU的占用率在99%以上。 性能瓶颈分析和优化分析12个ksoftirqd进程对应的内核空间占用率均为99%以上,远超基准值,提示建议为:“检查网络或驱动器堆栈是否存在瓶颈。“所以初步判断是应该是TaiShan服务器的SP580(IN200)网卡驱动问题,可以尝试通过安装或升级高版本网卡驱动解决问题。安装SP580网卡驱动步骤如下:1. 下载网卡驱动包。 下载地址:https://support.huawei.com/enterprise/zh/software/250631875-ESW20001342642. 上传网卡驱动包软件并解压。 driver为kmod驱动存放目录,本次选择driver/linux/nic/CentOS7.6_arm/kmod-hinic-2.3.2.1_4.14.0_115-1.el7.aarch64.rpm firmware为网卡固件存放目录 tools为hinicadm网卡工具目录 在安装驱动时,根据实际情况选择配套的工具和驱动包。3. 安装网卡工具hinicadm 执行rpm –ivh tools/linux_arm/nic/hinicadm-2.3.2.1-1.aarch64.rpm命令,显示如下信息表示网卡工具安装成功。 4. 卸载自带网卡驱动包 rmmod hinic5. 安装网卡驱动包 进入驱动包路径driver/linux/nic/CentOS7.6_arm/,执行如下命令安装网卡驱动。 rpm -ivh kmod-hinic-2.3.2.1_4.14.0_115-1.el7.aarch64.rpm 显示如下信息表示网卡安装成功 6. 加载驱动包 modprobe hinic7. 查看驱动版本信息 执行hinicadm version -i hinic0命令,查看驱动版本信息。 重新执行性能分析 重启全景分析任务,查看%soft指标已经降到0%,释放了被ksoftirqd占用的12个核,资源消耗降低9.27%调优结果分析本实践中,通过对TaiShan上CentOS7.6空载系统全景分析,发现有性能问题后,经过进程线程分析进步一步定位问题的瓶颈点,并通过安装高版本网卡驱动后,解决软中断问题,降低系统整体资源消耗。 在进行其他TaiShan+CentOS7.6上的应用调优之前,可以参考本实践,对系统进行整体的性能分析,降低不必要的资源消耗。
-
1. 问题描述 版本:GaussDB 6.5.1.9 客户跑批业务时,CN所在服务器经常出现TCP端口使用阈值告警,总共配置端口数量为32000+,会使用26000+,占比82%。2. 数据收集:查看系统随机端口资源:cat /proc/sys/net/ipv4/ip_local_port_range,操作系统默认在(32768, 61000);查询集群规模,统计集群共有多少台服务器,每台服务器部署多少主DN:cm_ctl query -Cv;统计Gauss进程信息:ps ux | grep gauss在任意一台TCP端口使用率过高的机器上连接CN进入数据库,查询并发数量:select count(*) from pg_stat_activity;查询当前CN与所有节点的连接数:select count(*) from pg_pooler_status;统计正常连接状态下的端口使用数量:netstat -npt | grep gauss | awk '{print $4}' | sort -n | uniq -c | wc -l;收集所有Gauss进程的TCP连接信息:netstat -anopt | grep gauss > result.log3. 问题分析 :根据集群规模以及并发数初步计算CN与DN之间的连接数量:436 * 270 = 117720;由于每一并发不一定需要与所有DN相连,实际CN、DN连接数量通过pg_pooler_status视图查询:62388;打开收集的Gauss进程的TCP连接信息,可根据对应的进程号,统计实际各个节点总的连接数量,例如:CN进程号为76649,搜索进程号,可看到实际有66371条连接;同理,DN与DN之间也存在着连接,数量为:4 * DN数量,也可根据DN进程统计连接数量;可以看出,Gauss连接数量远远大于可用端口数量,由于端口存在复用,实际使用数量为2.6中收集的:27922;4. 规避措施 :GaussDB所有进程均支持端口复用,端口使用率高也不会出现问题,但是如果在同台服务器上有第三方进程不支持端口复用时,随机端口耗尽后可能会出现报错。可根据情况评估后,选择是否屏蔽此项告警;降低并发数量,减少连接;5. 几个减少连接数量的方法(修改前要评估) :开启CN多流,GUC参数为comm_cn_dn_logic_conn;清理空闲连接,clean connection to all; 以上两种方法详见产品文档;
-
业务报错:terminating connection due to administrator command原因总结如下session_timeout查看报错的CN日志,如果有session unused timeout这样的日志,说明是会话超时导致的。解决办法:a) 连接CN查看超时时间,show session_timeout;b) 将session_timeout设置为0: set session_timeout=0; 该方式设置只对当前会话生效。如果想永久生效,需要使用如下命令进行设置:gs_guc reload -Z coordinator -Z datanode -N all -I all -c "session_timeout=0"线程收到SIGTERM信号a) 集群停止shutdown,ps ux查看gaussdb启动时间可以确认是不是该情况;b) 强制清理连接clean connection force: 这种情况不仅会清理idle连接,也会清理active连接c) 调用终止服务线程函数pg_terminate_backend(pid)。8.1上版本会在CN日志中打印pg_terminate_backend相关信息。有以下几种可能: 1)用户自己调用该接口,或其他脚本后台检测后杀掉。 2) 测磁盘超过阈值(默认90%)场景下,会调用命令“select pg_terminate_backend(pid) from pg_stat_activity where usesysid <> 10”终止当前CN上所有非omm用户的SQL,同时打印调用日志“cancel session ok”
-
问题如标题
-
PostgreSQL用户经常发现,服务端在连接数较大的情况下,会出现系统内存消耗过多的情况,严重者可能会造成OOM。但是服务端配置的共享内存(shared_buffers,wal_buffers等)是一定的,为什么内存会持续增加呢?这就与PostgreSQL的多进程架构有关了,下面我们来分析下。1. 大规格PG实例内存使用率较高分析为了保证物理内存能得到充分的利用,避免内存空间浪费,Linux把进程当前使用的内存部分加载到物理内存里,而不使用的部分则暂不加载。PostMaster进程注册共享内存时,系统只是分配一个虚拟的地址空间,并不直接分配物理内存。当有实际的内存访问时,CPU才会将虚拟地址映射到物理内存的一个地址上。维护这个映射关系的就是PageTable,它负责将虚拟内存地址转换成物理内存地址。Linux的内存管理采取的是分页存取机制:把较大的物理内存分为了一个个固定大小(4kB)的内存页进行管理。每块内存页通过PageTable中的一个元组来维护虚拟/物理内存之间的映射。CPU为了提高虚拟/物理内存之间的转换效率,也会在TLB中缓存一定量的Page Table元组。对于PostgreSQL这种多进程架构程序来说,当服务端使用的共享内存较大,且并发连接数较多时,由于操作系统对于每个进程都要维护单独的内存映射,PageTable中的元组数目将会变得非常多,所占用的内存大小也会特别大。2. Huge Page改善措施Linux为了应对这种场景,降低多进程下PageTable的内存消耗。自从2.6及以上内核版本提供了内存页大小为2MB的管理方式,称为Huge Page。如果使用Huge Page的话,相同物理内存使用量的情况下内存页的数目变少,减少了PageTable元组的条目个数,从而降低了系统的内存占用。作为世界上最先进的开源数据库,PostgreSQL也适配了Linux的Huge Page特性,服务端在注册共享内存时,会通过配置参数huge_pages来决定是否申请大页内存。postgresql.conf:huge_pages = on -- 注册共享内存时必须使用大页huge_pages = try -- 注册共享内存时首先考虑大页,若系统提供的大页内存不足时,则全部使用普通页huge_pages = off -- 注册共享内存时不使用大页真实应用场景:某PG用户将实例(shared_buffers = 64GB)部署在一台内存为256GB的ECS上,业务繁忙时ECS内存使用率为85%,PageTable占用内存120GB。而开启Huge Page后相同业务场景的内存使用率降低到50%以下,PageTable大小仅300M!3. PG实例开启Huge Page操作步骤(1)查看操作系统的Huge Page大小grep Hugepage /proc/meminfo(2)估算PostgreSQL实例需要的Huge Page使用量:128GB/2MB * 1.2 = 78643(3)/etc/sysctl.conf中添加:vm.nr_hugepages = 78643(4)重新加载系统配置参数:sysctl –p(5)确认是否配置成功。可以看到Huge Page总数为78643(6)确认PG配置文件打开huge_pages(7)启动PostgreSQL服务端,可以看到系统中的空闲Huge Page已经减少,部分大页已经被共享内存使用。4. Huge Page使用建议虽然Huge Page在一定场景下可以改善服务端内存使用过高的情况,但不是鼓励所有的PG实例都使用大页,盲目的开启Huge Page可能引起服务端的性能下降。下面我们根据Huge Page的优缺点来分析下使用场景。Huge Page优势:(1)CPU的TLB可以缓存的物理地址空间更大,从而提升TLB的命中率,降低CPU负载;(2)Huge Page使用的内存是不可交换(swap)的,没有内存空间换入/换出的开销;(3)极大的减少了系统维护PageTable的内存开销。Huge Page劣势:(1)Huge Page使用的内存需要预先分配;(2)Huge Page使用固定大小的内存区域,不会被释放;(3)对于写密集型的场景,Huge Page会加大Cache写冲突的发生概率。所以强烈推荐PG实例开启Huge Page的场景:共享内存使用较大(>=8GB)且连接数较多(>= 500),并且热点数据分散。不推荐PG实例开启Huge Page的场景:写业务密集,热点数据集中且内存使用较小。5.PG开启Huge Page时的注意事项(1)当配置参数huge_pages设置为on时,若PG启动时需要注册的共享内存大于操作系统提供的Huge Page大小时,数据库将无法启动。推荐将huge_pages参数设置为try,在此种场景下,PostMaster将会改为申请普通内存。(2)修改shared_buffers/wal_buffers等共享内存相关的GUC参数时,需要重新计算操作系统所需的Huge Page数,以防服务端无法启动或者部分大页内存没有被使用且无法释放而造成浪费。
-
问题描述:sql执行UDF函数报错,后台查看集群UDF进程,发现集群进程异常:cm_ctl query –CviF解决方法: 查看cm_agent日志,发现日志一直在拉起secbox进程,且日志中存在以下报错: df -h 查看磁盘挂载情况,发现/var/log/Bigdata是单独挂载的查看$GAUSSHOME/secbox/secbox.conf,发现配置文件中未对单独挂载的目录进行配置,导致了cm_agent的上述报错解决方案:[mount_path] read /var/log后面追加一条[mount_path] read /var/log/Bigdata的配置,等待几秒再检查一下集群状态,正常后把其他节点做同样的修改
-
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
-
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪
-
2020-10-26:线程池的线程数怎么设置比较好?#福大大#
-
①. 继承Thread类创建线程类定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。创建Thread子类的实例,即创建了线程对象。调用线程对象的start()方法来启动该线程。②. 通过Runnable接口创建线程类定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。调用线程对象的start()方法来启动该线程。③. 通过Callable和Future创建线程创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。使用FutureTask对象作为Thread对象的target创建并启动新线程。调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。来自:https://zhuanlan.zhihu.com/p/90026505
上滑加载中
推荐直播
-
OpenHarmony应用开发之网络数据请求与数据解析
2025/01/16 周四 19:00-20:30
华为开发者布道师、南京师范大学泰州学院副教授,硕士研究生导师,开放原子教育银牌认证讲师
科技浪潮中,鸿蒙生态强势崛起,OpenHarmony开启智能终端无限可能。当下,其原生应用开发适配潜力巨大,终端设备已广泛融入生活各场景,从家居到办公、穿戴至车载。 现在,机会敲门!我们的直播聚焦OpenHarmony关键的网络数据请求与解析,抛开晦涩理论,用真实案例带你掌握数据访问接口,轻松应对复杂网络请求、精准解析Json与Xml数据。参与直播,为开发鸿蒙App夯实基础,抢占科技新高地,别错过!
回顾中 -
Ascend C高层API设计原理与实现系列
2025/01/17 周五 15:30-17:00
Ascend C 技术专家
以LayerNorm算子开发为例,讲解开箱即用的Ascend C高层API
回顾中
热门标签