句柄的概念

1.这里将句柄所能标识的所有东西(如窗口、文件、画笔等)统称为“对象”。

2.图中一个小横框表示一定大小的内存区域,并不代表一个字节,如标有0X00000AC6的横框表示4个字节。

  

程序运行到某时刻的内存快照               程序往后运行到另一时刻时的内存快照

 

Windows是一个以虚拟内存为基础的操作系统,很多时候,进程的代码和数据并不全部装入内存,进程的某一段装入内存后,还可能被换出到外存,当再次需要时,再装入内存。两次装入的地址绝大多数情况下是不一样的。也就是说,同一对象在内存中的地址会变化。那么,程序怎么才能准确地访问到对象呢?为了解决这个问题,Windows引入了句柄。

系统为每个进程在内存中分配一定的区域,用来存放各个句柄,即一个个32位无符号整型值(32位操作系统中)。每个32位无符号整型值相当于一个指针,指向内存中的另一个区域(我们不妨称之为区域A)。而区域A中存放的正是对象在内存中的地址。当对象在内存中的位置发生变化时,区域A的值被更新,变为当前时刻对象在内存中的地址,而在这个过程中,区域A的位置以及对应句柄的值是不发生变化的。这种机制,用一种形象的说法可以表述为:有一个固定的地址(句柄),指向一个固定的位置(区域A),而区域A中的值可以动态地变化,它时刻记录着当前时刻对象在内存中的地址。这样,无论对象的位置在内存中如何变化,只要我们掌握了句柄的值,就可以找到区域A,进而找到该对象。而句柄的值在程序本次运行期间是绝对不变的,我们(即系统)当然可以掌握它。

所以,我们可以这么理解句柄:

数值上,是一个32位无符号整型值(32位系统下);

逻辑上,相当于指针的指针;

形象理解上,是Windows中各个对象的一个唯一的、固定不变的ID;

作用上,Windows使用句柄来标识诸如窗口、位图、画笔等对象,并通过句柄找到这些对象。

 

关于句柄,再交代一些关键性细节:

1.所谓“唯一”、“不变”是指在程序的一次运行中。如果本次运行完,关闭程序,再次启动程序运行,那么这次运行中,同一对象的句柄的值和上次运行时比较,一般是不一样的。

其实这理解起来也很自然,所谓“一把归一把,这把是这把,那把是那把,两者不相干”(“把”是形象的说法,就像打牌一样,这里指程序的一次运行)。

2.句柄是对象生成时系统指定的,属性是只读的,程序员不能修改句柄。

3.不同的系统中,句柄的大小(字节数)是不同的,可以使用sizeof()来计算句柄的大小。

4.通过句柄,程序员只能调用系统提供的服务(即API调用),不能像使用指针那样,做其它的事。

 

参考链接:

https://www.cnblogs.com/zpcdbky/p/4652151.html

 

Windows系统中有许多内核对象(这里的对象不完全等价于"面向对象程序设计"一词中的"对象",虽然实质上还真差不多),比如打开的文件,创建的线程,程序的窗口,等等。这些重要的对象肯定不是4个字节或者8个字节足以完全描述的,他们拥有大量的属性。为了保存这样一个"对象"的状态,往往需要上百甚至上千字节的内存空间,那么怎么在程序间或程序内部的子过程(函数)之间传递这些数据呢?拖着这成百上千的字节拷贝来拷贝去吗?显然会浪费效率。那么怎么办?当然传递这些对象的首地址是一个办法,但这至少有两个缺点:

 

 

 

暴露了内核对象本身,使得程序(而不是操作系统内核)也可以任意地修改对象地内部状态(首地址都知道了,还有什么不能改的?),这显然是操作系统内核所不允许的;

 

操作系统有定期整理内存的责任,如果一些内存整理过一次后,对象被搬走了怎么办?

 

所以,Windows操作系统就采用进一步的间接(可以理解为进一步的抽象的过程):在进程的地址空间中设一张表,表里头专门保存一些编号和由这个编号对应一个地址,而由那个地址去引用实际的对象,这个编号跟那个地址在数值上没有任何规律性的联系,纯粹是个映射而已。

 

 

 

在Windows系统中,这个编号就叫做"句柄"。 

 

Handle在Windows中的含义很广泛,以下关于谈到的Handle除非特别说明,将仅限于进程、线程的上下文中。

 

1、先来谈谈Handle

 

Handle本身是一个32位的无符号整数,它用来代表一个内核对象。它并不指向实际的内核对象,用户模式下的程序永远不可能获得一个内核对象的实际地址(一般情况下)。那么Handle的意义何在?它实际上是作为一个索引在一个表中查找对应的内核对象的实际地址。那么这个表在哪里呢?每个进程都有这样的一个表,叫句柄表。该表的第一项就是进程自己的句柄,这也是为什么你调用GetCurrentProcess()总是返回0x7FFFFFFF原因。

 

简单地说,Handle就是一种用来"间接"代表一个内核对象的整数值。你可以在程序中使用handle来代表你想要操作的内核对象。这里的内核对象包括:事件(Event)、线程、进程、Mutex等等。我们最常见的就是文件句柄(file handle)。

 

另外要注意的是,Handle仅在其所属的进程中才有意义。将一个进程拥有的handle传给另一个进程没有任何意义,如果非要这么做,则需要使用DuplicateHandle(),在多个进程间传递Handle是另外一个话题了,与这里要讨论的无关。

 

 

 

2、进程ID

 

首先,进程ID是一个32位无符号整数,每个进程都有这样的一个ID,并且该ID在系统范围内是唯一的。系统使用该ID来唯一确定一个进程。

 

深入些说,系统可能使用进程ID来计算代表该进程的内核对象的基地址(及EPROCESS结构的基地址),具体的计算公式你可以去问微软的OS开发人员。

 

3、HINSTANCE

 

HINSTANCE也是一个32无符号整数,它表示程序加载到内存中的基地址。

Windows是一个以虚拟内存为基础的操作系统,在这种环境下,Windows内存管理器经常在内存中来回移动对象,以此来满足各种应用程序的需要。对象被移动意味着它的地址变化了。由于地址总是如此变化,所以Windows操作系统为各应用程序腾出一些内存地址,用来专门登记各应用对象在内存中的地址变化,而这地址(存储单元的位置)本身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。

   因此,Windows程序中并不是用物理地址来标识一个内存块,文件,任务,或动态装入模块的,相反,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。

   在Windows编程中会用到大量的句柄,比如HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON(图标句柄)等。这当中还有一个通用的句柄,就是HANDLE,比如下面的语句:

1 HINSTANCE hInstance ;

2 HANDLE hInstance ;

  句柄地址(稳定)->记载着对象在内存中的地址->对象在内存中的地址(不稳定)->实际对象。但是,必须注意注意的是,程序每次重新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确是不一样的。

 

   而指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄,句柄是一种指向指针的指针。

  句柄和指针都是地址,不同之处在于:

(1)句柄所指的可以是一个很复杂的结构,并且很有可能是与系统相关的,比如说线程的句柄,它指向的就是一个类或者结构,它和系统有很密切的关系。当一个线程由于不可预料的原因而终止时,系统就可以返回它所占用的的资料,如CPU ,内存等。反过来想可以知道,这个句柄中的某一些项是与系统进行交互的。由于Windows系统是一个多任务的系统,它随时都可能要分配内存,回收内存,重组内存。

(2)指针也可以指向一个复杂的结构,但是通常是用户定义的,所以必须的工作都要用户完成,特别是在删除的时候
---------------------

 

linux文件句柄数

1问题阐述:

   too many open files:顾名思义即打开过多文件数。

不过这里的files不单是文件的意思,也包括打开的通讯链接(比如socket),正在监听的端口等等,所以有时候也可以叫做句柄(handle),这个错误通常也可以叫做句柄数超出系统限制。

当你的服务器在大并发达到极限时,就会报出“too many open files”。

查看线程占句柄数
ulimit -a

2、产生的原因:

经常在使用linux的时候出现,大多数情况是由于程序没有正常关闭一些资源引起的,所以出现这种情况,请检查io读写,socket通讯等是否正常关闭。

3、经典案例:

很多项目上线不久运行了一段时间后,服务突然宕了,经检查日志,出现了too many open files 错误。

 

 

4、解决方案:

前奏:其实Linux是有文件句柄限制的,而且默认不是很高,一般都是1024,作为一台生产服务器,其实很容易就达到 这个数量,因此我们需要把这个值改大一些。我们可以用ulimit -n 来查看当前用户句柄数限制。那么这个1024是系统的限制,还是用户的限制呢。其实,这个是用户限制来的,完整的说法,应该是当前用户准备要运行的程序的限制。 

1、这个限制是针对单个程序的限制 

2、这个限制不会改变之前已经运行了的程序的限制 

3、对这个值的修改,退出了当前的shell就会消失 

 因此出现这种问题有两种解决方式:

第一:增大文件句柄数。这种方式能及时解决问题,但是不能够彻底的解决问题,可以为彻底解决问题提供一定的时间保证。那么如何增大文件句柄数数呢? 

如修改文件句柄数为65535,ulimit -n 65535.此时系统的文件句柄数为65535. 
  2)将ulimit 值添加到/etc/profile文件中(适用于有root权限登录的系统) 
为了每次系统重新启动时,都可以获取更大的ulimit值,将ulimit 加入到/etc/profile 文件底部。 
  echo ulimit -n 65535 >>/etc/profile     
  source /etc/profile    #加载修改后的profile  
  ulimit -n     #显示65535,修改完毕! 
  到此为止,你以为大功告成了么,其实不然,突然发现自己再次登录进来的时候,ulimit的值还是1024,这是为什么呢? 用户登录的时候执行sh脚本的顺序: 
    /etc/profile.d/file 
    /etc/profile 
    /etc/bashrc 
    /mingjie/.bashrc 
    /mingjie/.bash_profile 

    由于ulimit -n的脚本命令加载在第二部分,用户登录时由于权限原因在第二步还不能完成ulimit的修改,所以ulimit的值还是系统默认的1024。所以想彻底改变这种问题,就必须做如下操作:修改/etc/security/limits.conf 

里面有很详细的注释,比如 

* soft nofile 2048 

* hard nofile 32768 

就可以将文件句柄限制统一改成软2048,硬32768 

那么什么是软限制,什么是硬限制 

硬限制是实际的限制,而软限制,是warnning限制,只会做出warning 

这样就实实际际的增大了文件句柄数。

第二:分析句柄数,查找原因,这是解决问题最根本的办法。那么如何分析那,就需要用到lsof这个命令了(关于这个命令大家可以在网上学习学习)。

  (1)统计各进程打开句柄数:lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr

     (2)统计各用户打开句柄数:lsof -n|awk '{print $3}'|sort|uniq -c|sort -nr

     (3)统计各命令打开句柄数:lsof -n|awk '{print $1}'|sort|uniq -c|sort -nr

 

查看系统打开句柄最大数量
more /proc/sys/fs/file-max
1
查看打开句柄总数
lsof|awk '{print $2}'|wc -l
1
根据打开文件句柄的数量降序排列,其中第二列为进程ID:
lsof|awk '{print $2}'|sort|uniq -c|sort -nr|more
1
根据获取的进程ID查看进程的详情
ps -ef |grep 
1
修改linux单进程最大文件连接数
修改linux系统参数。vi /etc/security/limits.conf 添加
*  soft  nofile  65536
*  hard  nofile  65536
修改以后保存,注销当前用户,重新登录,执行ulimit -a ,ok ,参数生效了:

 

posted @ 2019-03-18 22:13  konglingbin  阅读(7015)  评论(0编辑  收藏  举报