linux内核中听过就能记住的概念

简介:  打算给我们部门弄个内部分享。发现大家对一些底层知识的认知停留在一句一句的,比如听说JVM使用-XX:-UseBiasedLocking取消偏向锁可以提高性能,因为它只适用于非多线程高并发应用。使用数字对象的缓存-XX:AutoBoxCacheMax=20000比默认缓存-128~127要提高性能。对于JVM和linux内核,操作系统没有系统的概念,遇到实际问题往往没有思路。所以我的内部分享,主要分为linux部分,jvm部分和redis部分。这篇是linux篇。学习思路为主,知识为辅。我也是菜鸟一枚~~不过是个钻石心的菜鸟,不怕别人知道我有多菜。

 打算给我们部门弄个内部分享。发现大家对一些底层知识的认知停留在一句一句的,比如听说JVM使用-XX:-UseBiasedLocking取消偏向锁可以提高性能,因为它只适用于非多线程高并发应用。使用数字对象的缓存-XX:AutoBoxCacheMax=20000比默认缓存-128~127要提高性能。对于JVM和linux内核,操作系统没有系统的概念,遇到实际问题往往没有思路。所以我的内部分享,主要分为linux部分,jvm部分和redis部分。这篇是linux篇。学习思路为主,知识为辅。我也是菜鸟一枚~~不过是个钻石心的菜鸟,不怕别人知道我有多菜。


  先说为什么我要去学习linux内核。我在上家公司负责整个公司的搜索引擎。有一次很熟练的在一台虚拟机上新搭建了一套,压测到8000,额,报了一个NIO异常,说是:too many open files。当时查了一下,那台机器太破,和很多服务公用,内存快满了。所以换了台好点的机器就没有这个问题了。但是句柄超限到底是个什么东西呢?先来看看linux内核的一些基本概念。


  大局观嘛,先来看看unix的体系结构。


1112728-20170817204558740-1124839349.png


简单解释一下:任何计算机系统都包含一个基本的程序集合,它控制计算机硬件资源,提供程序运行环境。称为操作系统。在这个集合里,最重要的程序被称为内核,在系统启动时被装载。因为它相对较小,而且位于环境的核心。内核的接口被称为系统调用(system call)。公用函数库构建在系统调用接口之上,也可使用系统调用。shell是一个特殊的应用程序,为运行其他应用程序提供一个接口。


  一些操作系统允许所有的用户程序直接与硬件部分进行交互,如MS-DOS。但是类Unix操作系统在胡勇应用程序前把与计算机物理组织相关的所有底层细节隐藏了。当程序想使用硬件资源时,必须向操作系统发出一个请求,内核对这个请求进行评估,如果允许使用这个资源,内核代表应用程序与相关的硬件部分进行交互。为了实施这种机制,现代操作系统依靠特殊的硬件特性来禁止用户程序直接与底层硬件部分打交道,或者直接访问任意的物理地址。硬件为CPU引入了至少两种不同的执行模式:用户程序的非特权模式和内核的特权模式。Unix把他们分别称为用户态(User Mode)和内核态(Kernel Model)。


  我们平时敲的一些linux命令,实际上都是对应的内核的C语言函数。比如cat xxx | grep 'x'。这里面两个命令用|连接起来,这个叫做“管道”。先用男孩纸惯用的职业一点的语言介绍一下:管道是一个广泛应用的进程间通信手段。其作用是在具有亲缘关系的进程之间传递消息,所谓有亲缘关系,是指有同一个祖先。可以是父子,兄弟或者祖孙等等。反正只要共同的祖先调用了pipe函数,打开的管道文件会在fork之后,被各个后代所共享。其本质是内核维护了一块缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区内存的操作。分为匿名管道和命名管道。


  这里面包含了一些概念。进程的概念大家都应该很清楚:程序的执行实例被称为进程。UNIX系统确保每个进程都有一个唯一的数字表示符,称为进程ID(process ID),它是一个非负数。linux很多命令都会将其显示出来。有3个用于进程控制的主要函数:fork,exec和waitpid。其中fork函数用来创建一个新进程,此进程是调用进程的一个副本,称为子进程。fork对父进程返回新的子进程的进程ID(一个非负整数),对子进程则返回0。因为fork创建一个新进程,所以说它被调用一次,但返回两次。


  一个进程内的所有线程共享同一地址空间,文件描述符,栈以及进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。说到这里大家都应该多少有些概念了:为什么进程开销大,线程涉及锁。


  匿名管道是一个未命名的,单向管道,通过父进程和一个子进程之间传输数据。只能实现本地机器上两个进程之间的通信,而不能实现跨网络的通信。常用的比如linux命令。


  命名管道是进程间单向或双向管道,建立时指定一个名字,任何进程都可以通过该名字打开管道的另一端,可跨网络通信。


1112728-20170818162043787-1282729472.png

这是一个jvisualvm调试的截图,蓝框部分就相当于一个命名管道。

 

  好,现在来回答一个问题:用户进程间通信主要哪几种方式?


  刚才说的匿名管道和命名管道都算一种。除此之外,还有:信号,消息队列,共享内存,信号量和套接字。不用头疼,看到最后你很可能会有豁然开朗的感觉,学的东西终于可以串在一起了。


  信号(signal):其实是软中断信号的简称。用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达。


  收到信号的进程对各种信号有不同的处理方法,主要是三类:


  1>类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。


  2>忽略某个信号,对该信号不做任何处理。


  3>对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是让进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。


  下面是window的信号列表


1112728-20170818163940631-1169062557.png


linux也是用kill -l命令:


1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL

5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE

9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2

13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD

18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN

22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ

26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO

30) SIGPWR      31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1

36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4  39) SIGRTMIN+5

40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8  43) SIGRTMIN+9

44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13

48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13

52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9

56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6  59) SIGRTMAX-5

60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2  63) SIGRTMAX-1

64) SIGRTMAX


  我在用gdb命令运行调试C语言程序的时候经常可以看到这些信号量。


再来看消息队列。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立的接收含有不同类型的数据结构。可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列和命名管道一样,每个数据块都有一个最大长度的限制。


  共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到他们自己的地址空间中,所有进程都可以访问共享内存中的地址。


  信号量:为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式的执行。而信号量就可以提供这样的一种访问机制。让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来协调对共享资源访问的。


  套接字:这种通信机制使得客户端/服务器的开发工作既可以在本地单机上进行,也可以跨网络进行。它的特性有三个属性确定:域(domain),类型(type)和协议(protocol)。


简单的说:源IP地址和目的IP地址以及源端口号和目的端口号的组合成为套接字。


  下面介绍一下通信过程,里面涉及一些C语言的函数,不用怕,眼熟即可。如果你学习过nio,你会发现这些是很常接触的。


  要想使不同主机的进程通信,就必须使用套接字,套接字是用socket()函数创建,如果需要C/S模式,则需要把server的套接字与地址和端口绑定起来,使用bind(),当上述操作完成后,便可使用listen()来监听这个端口,如果有其他程序来connect,那么server将会调用accept()来接受这个申请并为其服务。client是调用connect()来建立与server之间的连接,这时会使用三次握手来建立一条数据链接。当连接被建立后,server与client便可以通信了,通信可以使用read()/write(),send()/recv(),sendto()/recvfrom()等函数来实现,但是不同的函数作用和使用位置是不同的。当数据传送完后,可以调用close()来关闭server与client之间的链接。


  到此,本篇文章的主要内容就没有了,基本就在介绍一个东西:linux内核的进程通信。这是学习任何高级编程语言nio部分的基础。下面引入一些辅助理解的概念。


  文件句柄:在文件I/O中,要从一个文件读取数据,应用程序首先要调用操作系统函数并传送文件名,并选一个到该文件的路径来打开文件。该函数取回一个顺序号,即文件句柄(file handle),该文件句柄对于打开的文件是唯一的识别依据。一个句柄就是你给一个文件,设备,套接字(socket)或者管道的一个名字,以便帮助你记住你证处理的名字,并隐藏某些缓存等的复杂性。说白了就是文件指针啦。


  文件描述符:内核利用文件描述符来访问文件。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。文件描述符形式上是非负整数,实际上它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符往往值适用于unix,linux这样的操作系统。习惯上,标准输入的文件描述符是0,标准输出是1,标准错误是2.


`/letv/apps/jdk/bin/java -DappPort=4 $JAVA_OPTS -cp $PHOME/conf:$PHOME/lib/* com.letv.mms.transmission.http.VideoFullServerBootstrap $1 $3 > /dev/null 2>&1 &`


自己部署过java后台程序的话,对上面的shell命令应该都能理解。 /dev/null 2>&1 这里面的2就是文件描述符,这个是将错误输出到文件。


  这两个概念比较绕,不用过多区分,可以当成一回事来理解。打开文件(open files)包括文件句柄但不仅限于文件句柄,由于lnux所有的事务都以文件的形式存在,要使用诸如共享内存,信号量,消息队列,内存映射等都会打开文件,但这些不会占用文件句柄。查看进程允许打开的最大文件句柄数的linux命令:ulimit -n

 

  好了,今天的概念都介绍完了,回到最初的问题:too many open files。 当时的机器破,内存快满了。所以搜索引擎走的是索引文件,有很多的IO操作,共享内存和内存映射这块的文件肯定是供不上的,报错了。萦绕在心头两年的问题稍微有点认知了。




相关文章
|
7月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
2月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
110 0
|
4月前
|
NoSQL Linux 编译器
GDB符号表概念和在Linux下获取符号表的方法
通过掌握这些关于GDB符号表的知识,你可以更好地管理和理解你的程序,希望这些知识可以帮助你更有效地进行调试工作。
181 16
|
5月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
229 67
|
4月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
91 20
|
3月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
97 0
|
3月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
63 0
|
3月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
65 0
|
5月前
|
存储 Linux
Linux内核中的current机制解析
总的来说,current机制是Linux内核中进程管理的基础,它通过获取当前进程的task_struct结构的地址,可以方便地获取和修改进程的信息。这个机制在内核中的使用非常广泛,对于理解Linux内核的工作原理有着重要的意义。
208 11
|
6月前
|
自然语言处理 监控 Linux
Linux 内核源码分析---proc 文件系统
`proc`文件系统是Linux内核中一个灵活而强大的工具,提供了一个与内核数据结构交互的接口。通过本文的分析,我们深入探讨了 `proc`文件系统的实现原理,包括其初始化、文件的创建与操作、动态内容生成等方面。通过对这些内容的理解,开发者可以更好地利用 `proc`文件系统来监控和调试内核,同时也为系统管理提供了便利的工具。
246 16