理解Unix进程

最近读了一本介绍Unix进程的小册子,虽然讲的是ruby的东西,但是对进程的概念,fork,父子进程,进程通信,信号量,IPC以及僵尸进程,守护进程概念讲的蛮有意思的,书名就叫《理解Unix进程》。

让我们先从一个 有趣的面试题说起,记得之前见到过一个面试题,如下:

如上所示,要求if里面写入一条语句,打印出true和false,也就是说要求if 和 else都执行,如果你从来没有接触过fork的话,很难想象出if,else竟然都可以执行?其实这只是假象,程序表面上if,else都执行了,其实是创建了2个进程,父进程执行了if,而子进程执行了else而已。

Unix最大的优点是,对很多东西进行了抽象,或者说标准化,比如对程序进行了抽象,叫做进程,java里说万事万物皆对象,那么对于Unix而言,就是万事万物皆文件了,除了对进程进行了抽象,还有对文件,对网络,对外部设备等等都抽象为资源,即文件,进程有进程ID即pid,文件也有唯一标示,叫文件描述符,有了文件描述符,就能对抽象的东西进行操作,比如读取,写入等,如果你关心资源描述符的话,你会发现,它其实就是一个ID,但是始终是从3开始的,为什么?因为系统已经为我们抽象出了3个资源,他们分别是0,1,2,分别代表着标准输入,标准输出,标准错误。

进程皆有父,Unix里的每个进程都有一个父进程,通过ppid标示,最开始的进程叫init进程,pid是多少?你猜,老大当然是1,它的父进程是谁?这你都敢问?你说上帝的上帝是谁?宇宙的宇宙是神马?不过如果你非要知道的话,它的ppid是0.

现在回到我们一开始的fork系统调用上来,fork简直是Unix最伟大的发明,没有之一,它简单又高效,实现了一生二,二生四,四生万物的最简单法则,如果没有fork,那么你神马都得不到。

每一个进程都可以通过fork来产生子进程,同时无私的将自己的内存和代码复制给了子进程,注意是复制,不是共享,也就是说,从fork内核调用之后,子进程将和父进程一起运行下去,父进程占用了100M内存,子进程也会有一份自己的100M内存,如果我fork一万次,怎么办?现代操作系统用很聪明的方法,解决了内存复用和fork开销的问题,那就是COW,即copy on write,读我们可以共享,但是孩子,如果你一旦写,那就要写你自己那份,这即解决了fork时的系统代价问题,又解决了资源占用和并发问题,就像java里的并发集合里,也有一个copyonwrite的Array一样,有一些设计思想是相通的,语言只是武器,就像屠龙刀和柳树枝,高手只用柳树枝一样可以杀人。

进程间如何进行通信呢?如果我只是fork出子进程,而不能通信和管理,那岂不是乱了辈分,所以父进程可以通过系统调用,来和子进程通信,比如wait等待,等待子进程结束后返回的状态码,或者使用管道进行通信。我们shell里,经常使用的ls -a|grep xxx就是进程间管道通信最好的例子。

Unix还有一个比较有意思的机制就是信号,进程可以等待信号,就像我们发短息一样,而不用for循环一直浪费cpu去轮训一个状态,说到信号,不得不说的是一个我们经常使用的命令,kill 而kill的时候,我们经常会这样kill,即kill -9 这个9代表什么呢??比如说我们有时候,想知道程序的核心转储,即栈追踪,我们java里杀掉jvm往往会这样,kill -3,而3又代表什么呢?

原来,系统内置了很多信号,比如Stop,Core,Term,而3就代表着Core的系统信息,也可以使用Ctrl-/来发出,9代表着Term,即立即结束,虽然进程可以截获信号,并选择忽略,但是唯有Stop和Term是不能截获和忽略的,这也是为什么kill -9才是老大.

对了,还有一个系统调用,exec没有说,这个就留给能坚持读到这的你去了解下它和fork的区别和配合吧。

对iOS程序ipa文件反汇编

这2天一直在和12306斗智斗勇,为了回家买张车票不容易啊,发现12306客户端验证码比较简单,比起网站的那个变态彩色验证码要更容易OCR一些,于是拦截网络请求一看,验证码请求返回的是加密数据,一开始我以为是base64编码,但是发现其实不符合base64编码规范的,肯定是加密过的数据,于是想安卓可以反编译成java文件,看看怎么编码的,于是解压包之后,发现尼玛是libcheckcode.so,好吧,人家12306现在也不是吃素的了,安全加密技术也是很到位的了,但是不死心,.so虽然源码看不到,但是总是有java调用的,白盒看不到,黑盒总行吧,虽然还是需要输入验证码,但是可以提前准备好数据,输入验证码立即提交就好了。

安卓是个妥协的方案,但是是最容易最现实的,但是难道iOS只能眼睁睁抓瞎吗?NO,NO,NO,虽然ipa二进制的想反编译成OC,几乎不可能,但是反汇编总是可行的吧?

于是搜了下,发现比较成熟的是IDA Pro,但是尼玛要好几百刀,你让我们这些屌丝情何以堪,完全用不起有木有?难道这些都是给高富帅的工具吗?但是最终功夫不负有心人,终于找到了屌丝神器,那就是Hopper,翠花上截图:

Hopper效果还不错吧?基本的汇编代码和流程图都有了,可惜目前还完全看不懂这天数一样的大量汇编代码,努力ing,至少向前迈进了一步。

 

Objective-C学习笔记

objective-c学习笔记

  • 使用import <xxx/xxx.h>的方式来引入头文件,结构体等
  • .m结尾的文件,m代表message,表示其有消息的特性
  • Foundation框架是基础功能框架,提供了类似string,集合等一些基础功能,记得F是大写的
  • NSLog用来输出控制台文本,类似println,同时@”hello world”代表的是NSString,是Objective-c的字符串类
  • NS前缀是为了防止函数名冲突,NS代表NextSTEP
  • BOOL类型的YES和NO,本质是byte,0代表NO
  • BOOL是对象,使用%@输出
  • 每个对象都可以实现一个叫description的方法,类似java的toString,BOOL对象也不例外
  • Objective-C是面向对象的语言,所以你在java学到的面向对象的一些技巧,和设计模式一样适用
  • 第三章的第一节就是c语言的例子,解释拙劣的间接概念,食之无味
  • 第三章的第二节例子还不错,讲的是面向过程和面向对象的区别,以及维护程序的开闭原则,对修改需要封闭,对添加需要是开放的
  • id 类似java的Object对象,可以指向任何对象
  • 调用方法,使用发送消息机制,[obj method]
  • @interface 类名 : NSObject和java的interface不同,类似java的class,@interface可以有成员变量,类似java的抽象类
  • 记得任何类定义后面以@end结尾
  • 使用{}声明属性,- (返回参数类型) 方法名:(参数类型) 参数名称
  • @implementation 实现类,不需要有实现类类名
  • self类似java的this
  • 可以通过对类发送new消息创建对象,类似这样 [Xxx new]
  • 第四章继承没啥特殊的,调用父类方法一样使用的是super
  • 第五章组合,我喜欢使用组合,而不是复合这个名词
  • 不要将get用作getter方法的前缀,get往往意味着传递的是参数指针,而不是通过返回值获取内容
  • 第四章和第五章无非就说了面向对象的isa和hasa
  • 第六章 源文件组织 群组的概念,虚拟组装文件分类的方式,不一定和物理文件对应
  • import文件<>和””的区别是<>代表的是系统头文件,而后一个是自己的项目文件
  • @class和@import的区别,在于符号链接和预编译的区别,也就是将实际类型推迟到运行时,还是编译级别就需要感知
  • 第七章 深入了解Xcode 无他唯手熟而
  • 第八章 Foundation Kit介绍
    • 可以使用+来声明类方法,例如NSString的 stringWithFormat
    • NSString的方法isEqualToString和compare以及length方法
    • NSString是不可变的和java的一样,可以使用NSMutableString替代,类似StringBuilder,可以appendString
    • 集合类:NSArray和NSDictionary,NSArray和NSDictionary是不可变的,如果需要动态变化,需要加上Mutable
    • 可以通过arrayWithObjects类方法创建数组,并以nil结尾代表结束,所以数组不能存储nil以及基本类型,如int,enum
    • 也可以以字面量创建数组,类似这样@[@”xxx”,@”sdad”],则不需要使用nil结尾
    • 枚举可以通过NSEnumerator,也可以快速枚举,in的形式
    • NSNumber以及NSValue和NSNull
  • 第九章 内存管理
    • 对象的生命周期,引用计数
    • 每个对象都有一个引用计数器,当通过new,alloc或者copy创建一个对象时,其引用计数器为1,可以通过retain使其+1,release使其-1,每当对象的引用计数器为0,将要被销毁的时候,将会对其发送dealloc消息,你可以重写dealloc方法,以便释放对象申请的资源,可以通过retainCount获取引用计数的值
    • 对象所有权,谁创建谁负责,传递引用需要根据情况判断是否retain
    • 访问方法中的保留和释放,原则是先对传入的retain再对老的对象release,然后新的赋值给老的引用变量
    • 自动释放,@autoreleasepool以及NSAutoReleasePool,其实就是把每次release的消息延迟到最后,统一发送而已
    • 内存管理的三条规则:
      • 当你使用new,alloc或者copy创建一个对象时,该对象的保留计数器值为1,当你不再使用该对象时,需要对其发送一条release或者autorelease消息
      • 当你通过其它方法获得一个对象的时候,如果你打算一段时间内拥有它,你需要retain它,以防别人release掉,然后在你不再使用之后,release它
      • 如果你保留了某个对象,最终你需要释放或者自动释放它,必须确保retain和release的方法次数对等
    • iOS开发不能使用自动垃圾回收,而是使用自动引用计数(ARC),ARC只对可保留的对象指针有效
      • 代码块指针
      • Objective-C对象指针
      • 通过__attribute__((NSObject))类型定义的指针
    • 如果想在代码中使用ARC,必须满足以下三个条件
      • 能够确定哪些对象需要进行内存管理
      • 能够表明如何去管理对象
      • 有可行的办法传递对象的所有权
    • 弱引用,可以避免循环引用而得不到释放的情况,如果软引用的对象被释放了,会导致使用出问题,可以使用归零弱引用,可以使用关键字_weak或者属性@property(weak),只对ios5和10.7以上有效
    • 使用ARC的时候,有2种命名规范:属性名称不能以new开头;属性不能只有一个read-only而没有内存管理特性
    • 异常,NSException,@try @catch @finally @throw
  • 第十章 对象初始化
    • 分配和初始化,使用alloc分配内存而使用init初始化对象
    • 使用一条语句分配和初始化对象:Car *car=[[Car alloc]init]
    • [super dealloc]应该为dealloc方法的最后一条语句,并且不要忘记
  • 第十一章 属性
    • 可以通过@property标示一个类的属性,而不需要写getter和setter了,实现类里可以使用@synthesize标示,但是Xcode4.5之后就可以不使用@synthesize
    • 点表达式的使用,可以类似java一样存取属性,编译器会处理getter和setter调用
    • 属性扩展,@property(copy) @property(retain) @property(assign)
    • 名称的使用,如果实例变量和局部变量名称不一致,则可以这样@synthesize 访问方法名=实例变量名
    • 只读属性,@property默认是readwrite的,也可以单独设置为readonly
    • 可以使用@dynamic标示不需要自动生成getter和setter方法
  • 第十二章 类别
    • 类别是为现有的类添加新方法的方式
    • 把类别代码单独放在一个文件中,以类名称+类别名称命名
    • 添加方法,@interface 类名(类别名称),可以通过@dynamic添加属性
    • #import “类名称+类别名称”.h
    • 类别的缺陷:无法添加实例变量,方法覆盖
    • 类扩展,可以添加实例变量,可以改变只读属性,但是只在类内部有效,作用可以对外隐匿实现
    • 利用类别可以分散实现代码
    • 通过类别创建前向引用,当访问没有在接口声明的实例方法时,编译器会给出警告,消除警告的方法是通过类别声明方法,但是规范要求不能访问私有方法,所以最好不要这么干
    • 非正式协议:创建一个NSObject的类别称为创建一个非正式协议
    • 响应选择器,如何知道一个对象是否可以响应一个消息呢?可以使用@selector(),并且可以使用NSObject的respondsToSelector方法测试
  • 第十三章 协议
    • 协议类似java的接口,通过@protocol声明,协议可以继承协议,通过 @protocol Xxx <Father>
    • 类声明实现某个协议时通过后面跟 <协议名>实现,多个协议之间通过,分割

java日志:commons-loging,log4j,slf4j,LogBack的区别

Commons-logging只是一个API,定义了接口,不负责具体的实现,而具体的实现有以下几个:log4j,slf4j,logback

先简单说下Commons-logging的查找日志实现的顺序:

1.   先找org.apache.commons.logging.LogFactory 属性配置

2.  利用service 发现机制,扫描classpah 下的META-INF/services/org.apache.commons.logging.LogFactory 文件,若找到则使用里面的配置。

3.   否则,从Classpath 里寻找commons-logging.properties ,找到则根据里面的配置加载。

4.   否则,使用默认的配置:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用JDK14Logger 实现,再没有则使用commons-logging 内部提供的SimpleLog 实现。

从上述加载流程来看,如果没有做任何配置,只要引入了log4j 并在classpath 配置了log4j.xml ,则commons-logging 就会使log4j ,而代码里不需要依赖任何log4j 的代码。

所以可以看出,使用Log4J主要是log4j.xml配置文件的编写

Log4J配置文件的基本格式如下:


 

其中 [level] 是日志输出级别,共有5级:


 

Appender 为日志输出目的地,Log4j提供的appender有以下几种:


 

Layout:日志输出格式,Log4j提供的layout有以下几种:


 

打印参数: Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,如下:


Slf4j : 全称为Simple Logging Facade for JAVA, 是对不同日志框架提供的一个Facade封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。

slf4j的代理架构图

slf4j的代理架构图

slf4j的jar 包里存放了配置META-INF/services/org.apache.commons.logging.LogFactory =org.apache.commons.logging.impl.SLF4JLogFactory,而commons-logging 在初始化的时候会找到这个serviceId ,并把它作为LogFactory 。

完成桥接后,那么SLF4J 内部又是如何来装载合适的log 呢?

原理是SLF4J 会在编译时绑定import org.slf4j.impl.StaticLoggerBinder; 该类里面实现对具体日志方案的绑定接入。任何一种基于slf4j 的实现都要有一个这个类。如:

org.slf4j.slf4j-log4j12-1.5.6: 提供对 log4j 的一种适配实现。

Org.slf4j.slf4j-simple-1.5.6: 是一种 simple 实现,会将 log 直接打到控制台。

……

那么这个地方就要注意了:如果有任意两个实现slf4j 的包同时出现,那就有可能酿就悲剧,你可能会发现日志不见了、或都打到控制台了。原因是这两个jar 包里都有各自的org.slf4j.impl.StaticLoggerBinder ,编译时候绑定的是哪个是不确定的。这个地方要特别注意!!

Logback是由log4j创始人设计的又一个开源日志组件,性能进行了优化,log4j和logback的对比:

结果分析:

从测试结果可见:

如果不开启AsyncAppender,Log4j和Logback的性能不相上下;如查开启了AsyncAppender,Logback的性能将有非常大的提升。

关闭immediateFlush,在开启AsyncAppender的情况下,对Logback的影响很小,但对Log4J的影响却很大;但在不开启的AsyncAppender的情况下,对二都影响都很大。

原因是Logback对AsyncAppender做了很多优化,主要对并发处理方式的不同。二者都采用了生产者、消费者模型。

Log4j的实现原理如下:

Logging Event进入AsyncAppender,AsyncAppender会调用append方法,在append方法中会去把logging Event填入Buffer中,当消费能力不如生产能力时,AsyncAppender会把超出Buffer容量的Logging Event放到DiscardSummary中,作为消费速度一旦跟不上生成速度,中转buffer的溢出处理的一种方案。

AsyncAppender有个线程类Dispatcher,它是一个简单的线程类,实现了Runnable接口。它是AsyncAppender的后台线程。

Dispatcher所要做的工作是:

(1)锁定Buffer,让其他要对Buffer进行操作的线程阻塞。
(2)看Buffer的容量是否满了,如果满了就将Buffer中的Logging Event全部取出,并清空Buffer和DiscardSummary;如果没满则等待Buffer填满Logging Event,然后notify Disaptcher线程。

(3)将取出的所有Logging Event交给对应appender进行后面的日志信息推送。

于是Log4J用ArrayList实现了一个buffer队列(@see org.apache.log4j.AsyncAppender#append)来用户缓冲,并用一堆的synchronized来保证线程安全,对于AppenderAttachable则采用Vector来保证线程安全。

Logback的整体原理与Log4J基本相似,但直接使用了并发包的ArrayBlockingQueue来实现Buffer缓冲,对于AppenderAttachable并没采用加锁的机制来保证线程安全,而是通过读写分离来实现了线程安全,具体用的是CopyOnWriteArrayList。

综于以上原因,不难理解为什么异步下Logback比Log4J性能高出如此多。

结论:

考虑到Logback在多线程情况下的性能优秀表现,加上接口使用的方便性,强烈建议新应用接入Logback。老的应用如果日志没有成为系统的性能瓶颈,考虑到迁移成本较高,可以保留现状。

主要的CPU指令集体系结构

令 目前主要的CPU指令集体系结构有5种:X86系列,ARM系列,MIPS系列,Power系列,以及C6000

X86系列是以PC市场起家的,主要供应商为Intel和AMD,后续由于Intel的发展壮大,逐渐进入服务器和嵌入式领域.x86属于CISC

ARM主要面向与手机终端等低功耗的领域,ARM指令集体系结构是由MIPS借鉴而来,由英国的一家公司制定,此公司只授权指令集的使用,具体CPU内部架构和实现由厂商自己来做,比较著名的有德州仪器,三星电子,ARM属于RISC

MIPS是最经典的RISC,设计非常优雅,最初来自工作站,后来发展不景气,加上Intel的竞争,所以知名度一直不高,据说龙芯就是采用的MIPS的指令集

Power是IBM的服务器产物,后来IBM想冲击PC领域,后来和摩托罗拉,苹果一起成了了AIM联盟,苹果早期一直采用的都是PowerPC的CPU,后来才使用了x86系列的,Power系列的都是RISC

C6000这个比较不常见,是因为它是专有领域的CPU,它擅长的领域在于DSP即数字信号处理,比如无线通信,音视频技术等,知名厂商就是TI

JMX简介

管 根据百度百科的定义,JMX是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。通常使用JMX来监控系统的运行状态或管理系统的某些方面,比如清空缓存、重新加载配置文件等

JMX体系结构分为以下四个层次:设备层,代理层,分布服务层和附加管理协议API,目前从JDK1.5开始只提供了前2种实现
设备层又叫构件层,具体来说就是MBean,MBean是什么?就是对外暴露,需要被管理的JavaBean.
MBean又分标准MBean,动态MBean,开放MBean,以及模型MBean,其中动态MBean和开放MBean用的比较少,我们重点介绍标准MBean和模型MBean。

标准MBean,只要遵循MBean的规范的JavaBean就是标准MBean,具体是什么规范呢,就是首先定义一个以MBean结尾的XXXMBean接口,此接口内的所有方法都将可以被调用访问,然后定义类XXX实现XXXMBean接口,例子如下:

Count是需要被管理的类 

包含在MBean中方法都将是可以被管理的,MBean起名是有规范的,就是原类名后加上MBean字样

jmxtools.jar的下载地址为下载,运行 Main之前需要先启动rmiregister,命令行输入:rmiregistry 8888

另外需要添加JVM启动参数

-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.port=8880

第一个参数代表是否支持远程链接,第二个参数代表是否使用用户密码验证,如果为true,需要另外2个参数

-Dcom.sun.management.jmxremote.password.file=/home/wdw/jmx/jmxremote.password
-Dcom.sun.management.jmxremote.access.file=/home/wdw/jmx/jmxremote.access

其中jmxremote.password文件内容可以为

monitorRole <Your password>
controlRole <Your password>

jmxremote.access内容为

monitorRole readOnly
controlRole readWrite

ssl的参数是否启用ssl加密支持
然后可以启动jconsole,如果本地启动,可以看到本地进程,如果是远程连接,则可以输入地址URL:service:jmx:rmi:///jndi/rmi://localhost:8888/server
来启动远程管理,然后在JMX管理里可以看到。如果使用http管理,可以直接在浏览器里输入http://localhost:8080就可以了

动态MBean:运行时动态添加管理方法,我们上面的例子里,stop方法执行之后,就会发现count不能再累加了,我们希望stop执行之后,可以手动执行+1的操作,例子如下:

不做过多解释了,看代码应该能看得懂,动态MBean的代码稍微复杂点,但它可以动态组装MBean信息,所以有时候可以动态做一些事情。

模型MBean:经过上面几次折腾之后,你会发现一个问题,被管理的Bean需要实现一个接口,但是,有时候被管理的Bean我们可能无法拿到源代码,对它进行修改的,这时候,我们就需要使用模型MBean了。例子如下

 

Count不需要实现任何接口,比如不能修改源码的Service,我们增加一个Util类来组装RequiredModelMBean

具体代码和动态MBean差不多,不再细述,最后使用方式和之前差不多

 配置的MBean:模型MBean可以动态管理一个已有的JavaBean,但是写起来还是很麻烦,可以不可以采用配置的方式呢?答案是肯定的,配置的方式有2种,一个是采用apache的commons-modeler,另一个是结合Spring,与Spring集成

先来看commons-modeler怎么做

虽然不需要实现接口,但是JavaBean规范还是需要的,比如get set方法

我们使用 XML来描述信息:mbeans.xml

放在和Count同一个包路径下就行

这是基本的Modeler的使用了。

Spring集成:关于和Spring的集成,我只把spring的配置文件写下,由于容器启动需要配合,就不再执行了

结合Spring的配置,很简单是吧?感兴趣的同学可以在应用里自己试试

另外关于JMX知识的最后一点是Notification机制,简单来说,就是一个MBean属性或者方法被调用之后会通知关心这个事件的监听者,来达到消息通知的目的,具体细节,就先不写了,等后续有时间再补充吧。

编译memcached项目

码 最近需要研究优化对memcached的使用,于是就将其源代码git clone了下来,发现源码的编译是使用automake来生成MakeFile文件的,除了编译是需要automake之外,memcached还需要安装一些依赖,比如libevent,于是又将libevent代码拉下来,进行编译,而libevent也是基于automake的,不过它还使用了libtool来生成动态链接库,虽然使用automake来生成Makefile但是还是准备了autogen.sh脚本来完成,因此编译起来还是比较方便的

虽然很容易就完成了编译过程,但是对automake和autoconf还是一知半解,所以就简单学习了下

流程大概就是这样子的,待后续需要时再系统学习下各种宏定义吧

为Ubuntu12.04添加程序启动器

快速启动图标是程序的入口,ubuntu在这方面有点欠缺,不过好在ubuntu12.04的unity提供了一种可以将启动器添加到dash broad上的功能。

dash broad上每个图标对应/usr/share/applications目录中的一个配置文件,每个文件的后缀为.desktop,要添加一个自定义程序启动器,只需要在该文件目录下创建对应的配置文件即可,至于配置文件的语法,可以参考该目录下的配置文件。

以添加eclipse启动器为例:

  1. 在/usr/share/applications下创建eclipse启动器配置文件

     
  2. 编辑eclipse.desktop并保存,示例如下:

    使用WIN键,搜索eclipse,左键托拽到dash上就可以了