android framework层本来提供了SerialPort和SerialManager两个类来操作串口,本文提供的是一种利用jni操作串口的方法,app层也可以使用。言归正传,下面来详细说下过程:

一:在frameworks/base/serivces/core/java/com/android/server 新建文件夹tnport,里面分别有

1:ComBean
package com.android.server.tnport;

import java.text.SimpleDateFormat;

/**
 * Author:    ZHY
 * Email:     2892478492@qq.com
 * Time:      2017/10/12. 13:49
 * Package:   com.android.server.tnport
 * FileName:  ComBean
 */

public class ComBean {public byte[] bRec = null;
   public String sRecTime="";
   public String sComPort="";

   public ComBean(String sPort,byte[] buffer,int size) {sComPort = sPort;
      bRec = new byte[size];
      for (int i = 0; i < size; i++) {bRec[i] = buffer[i];
      }SimpleDateFormat sDateFormat = new SimpleDateFormat("hh:mm:ss");
      sRecTime = sDateFormat.format(new java.util.Date());
   }
}
2:MyFunc
package com.android.server.tnport;

public class MyFunc {//-------------------------------------------------------

    static public int isOdd(int num){return num & 0x1;
   }//-------------------------------------------------------
    static public int HexToInt(String inHex)//
    {return Integer.parseInt(inHex, 16);
    }//-------------------------------------------------------
    static public byte HexToByte(String inHex)//
    {return (byte)Integer.parseInt(inHex,16);
    }//-------------------------------------------------------
    static public String Byte2Hex(Byte inByte){return String.format("%02x", inByte).toUpperCase();
    }//-------------------------------------------------------
   static public String ByteArrToHex(byte[] inBytArr){StringBuilder strBuilder=new StringBuilder();
      int j=inBytArr.length;
      for (int i = 0; i < j; i++){strBuilder.append(Byte2Hex(inBytArr[i]));
         strBuilder.append(" ");
      }return strBuilder.toString();
   }//-------------------------------------------------------
    static public String ByteArrToHex(byte[] inBytArr,int offset,int byteCount){StringBuilder strBuilder=new StringBuilder();
      int j=byteCount;
      for (int i = offset; i < j; i++){strBuilder.append(Byte2Hex(inBytArr[i]));
      }return strBuilder.toString();
   }static public byte[] HexToByteArr(String inHex){int hexlen = inHex.length();
      byte[] result;
      if (isOdd(hexlen)==1){hexlen++;
         result = new byte[(hexlen/2)];
         inHex="0"+inHex;
      }else {result = new byte[(hexlen/2)];
      }int j=0;
      for (int i = 0; i < hexlen; i+=2){result[j]=HexToByte(inHex.substring(i,i+2));
         j++;
      }return result;
   }
}
3:SerialHelper
package com.android.server.tnport;

import android.util.Slog;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;

public abstract class SerialHelper {private static final String TAG = "SerialHelper";
   private SerialPort mSerialPort;
   private OutputStream mOutputStream;
   private InputStream mInputStream;
   private ReadThread mReadThread;

   private SendThread mSendThread;
   private String sPort = "/dev/ttyS4";
   private int iBaudRate = 9600;
   private boolean _isOpen = false;
   private byte[] _bLoopData = new byte[]{0x30};
   private int iDelay = 500;

   //----------------------------------------------------
   public SerialHelper(String sPort, int iBaudRate) {this.sPort = sPort;
      this.iBaudRate = iBaudRate;
   }public SerialHelper() {this("/dev/ttyS4", 9600);
   }public SerialHelper(String sPort) {this(sPort, 9600);
   }public SerialHelper(String sPort, String sBaudRate) {this(sPort, Integer.parseInt(sBaudRate));
   }//----------------------------------------------------
   public void open() throws SecurityException, IOException, InvalidParameterException {Slog.e(TAG, "open port");
      mSerialPort = new SerialPort(new File(sPort), iBaudRate, 0);
      mOutputStream = mSerialPort.getOutputStream();
      mInputStream = mSerialPort.getInputStream();
      mReadThread = new ReadThread();
      mReadThread.start();
      mSendThread = new SendThread();
      mSendThread.setSuspendFlag();
      mSendThread.start();
      _isOpen = true;
   }//----------------------------------------------------
   public void close() {if (mReadThread != null)mReadThread.interrupt();
      if (mSerialPort != null) {mSerialPort.close();
         mSerialPort = null;
      }_isOpen = false;
   }//----------------------------------------------------
   public void send(byte[] bOutArray) {try {mOutputStream.write(bOutArray);
      } catch (IOException e) {e.printStackTrace();
      }}//----------------------------------------------------
   public void sendHex(String sHex) {byte[] bOutArray = MyFunc.HexToByteArr(sHex);
      send(bOutArray);
   }//----------------------------------------------------
   public void sendTxt(String sTxt) {byte[] bOutArray = sTxt.getBytes();
      send(bOutArray);
   }//----------------------------------------------------
   private class ReadThread extends Thread {@Overridepublic void run() {super.run();
         Slog.e(TAG, "read thrad start run");
         while (!isInterrupted()) {try {try {Thread.sleep(1000);
               } catch (InterruptedException e) {e.printStackTrace();
               }if (mInputStream == null) return;
               byte[] buffer = new byte[512];
               int size = mInputStream.read(buffer);
               Slog.e(TAG, "size=" + size);
               if (size > 0) {ComBean ComRecData = new ComBean(sPort, buffer, size);
                  onDataReceived(ComRecData);
               }} catch (Throwable e) {e.printStackTrace();
               Slog.e(TAG, "ReadThread Throwable");
               return;
            }}}}//----------------------------------------------------
   private class SendThread extends Thread {public boolean suspendFlag = true;

      @Overridepublic void run() {super.run();
         Slog.e(TAG, "send thread run");
         while (!isInterrupted()) {synchronized (this) {while (suspendFlag) {try {wait();
                  } catch (InterruptedException e) {e.printStackTrace();
                  }}}send(getbLoopData());
            try {Thread.sleep(iDelay);
            } catch (InterruptedException e) {e.printStackTrace();
               Slog.e(TAG, "SendThread InterruptedException");
            }}}public void setSuspendFlag() {this.suspendFlag = true;
      }public synchronized void setResume() {this.suspendFlag = false;
         notify();
      }}//----------------------------------------------------
   public int getBaudRate() {return iBaudRate;
   }public boolean setBaudRate(int iBaud) {if (_isOpen) {return false;
      } else {iBaudRate = iBaud;
         return true;
      }}public boolean setBaudRate(String sBaud) {int iBaud = Integer.parseInt(sBaud);
      return setBaudRate(iBaud);
   }//----------------------------------------------------
   public String getPort() {return sPort;
   }public boolean setPort(String sPort) {if (_isOpen) {return false;
      } else {this.sPort = sPort;
         return true;
      }}public boolean isOpen() {return _isOpen;
   }public byte[] getbLoopData() {return _bLoopData;
   }public void setbLoopData(byte[] bLoopData) {this._bLoopData = bLoopData;
   }public void setTxtLoopData(String sTxt) {this._bLoopData = sTxt.getBytes();
   }public void setHexLoopData(String sHex) {this._bLoopData = MyFunc.HexToByteArr(sHex);
   }public int getiDelay() {return iDelay;
   }public void setiDelay(int iDelay) {this.iDelay = iDelay;
   }public void startSend() {if (mSendThread != null) {mSendThread.setResume();
      }}public void stopSend() {if (mSendThread != null) {mSendThread.setSuspendFlag();
      }}protected abstract void onDataReceived(ComBean ComRecData);
}
4:SerialPort
package com.android.server.tnport;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.util.Log;

public class SerialPort {private static final String TAG = "SerialPort";
   private FileDescriptor mFd;
   private FileInputStream mFileInputStream;
   private FileOutputStream mFileOutputStream;

   public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {Log.i(TAG, "----SerialPort--file="+device.getName()+"--baudrate="+baudrate+"--flags="+flags);
      if (!device.canRead() || !device.canWrite()) {try {/* Missing read/write permission, trying to chmod the file */
            Process su = Runtime.getRuntime().exec("/system/bin/su");
            String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"+ "exit\n";
            su.getOutputStream().write(cmd.getBytes());
            if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {throw new SecurityException();
            }} catch (Exception e) {e.printStackTrace();
            throw new SecurityException();
         }}else{Log.i(TAG, "---!device.canRead() || !device.canWrite()");
      }mFd = open(baudrate, flags, device.getAbsolutePath());
      if (mFd == null) {Log.e(TAG, "----null---native static FileDescriptor open(String path, int baudrate, int flags)");
         throw new IOException();
      }mFileInputStream = new FileInputStream(mFd);
      mFileOutputStream = new FileOutputStream(mFd);
   }// Getters and setters
   public InputStream getInputStream() {return mFileInputStream;
   }public OutputStream getOutputStream() {return mFileOutputStream;
   }// JNI
   private native static FileDescriptor open(int baudrate, int flags, String path);
   public native void close();
// static {
//    System.loadLibrary("serial_port");
// }
}
二:在自己的UsbAndSerialPortService中调用
private void openPort(SerialHelper ComPort) {try {ComPort.open();
   } catch (SecurityException e) {Slog.e(TAG, "open port failed: no permission");
   } catch (IOException e) {Slog.e(TAG, "open port failed: unknow error");
   } catch (InvalidParameterException e) {Slog.e(TAG, "open port failed: permeter error");
   }
}
private void closePort(SerialHelper ComPort) {if (ComPort != null) {ComPort.stopSend();
      ComPort.close();
   }
}
private class SerialControl extends SerialHelper {public SerialControl() {}@Overrideprotected void onDataReceived(final ComBean ComRecData) {Slog.e(TAG, "onDataReceived");
      DispQueue.AddQueue(ComRecData);

   }
}
private void sendProtData(SerialHelper ComPort, String sOut){if (ComPort != null && ComPort.isOpen()){ComPort.sendTxt(sOut);
   }
}
private class DispQueueThread extends Thread {private Queue<ComBean> QueueList = new LinkedList<ComBean>();

   @Overridepublic void run() {super.run();
      while (!isInterrupted()) {final ComBean ComData;
         while ((ComData = QueueList.poll()) != null) {DispRecData(ComData);
            try {Thread.sleep(1000);
            } catch (Exception e) {e.printStackTrace();
            }break;
         }}}public synchronized void AddQueue(ComBean ComData) {QueueList.add(ComData);
   }
}
private void DispRecData(ComBean ComRecData) {StringBuilder sMsg = new StringBuilder();
   sMsg.append(ComRecData.sRecTime);
   sMsg.append("[");
   sMsg.append(ComRecData.sComPort);
   sMsg.append("]");
   sMsg.append("[Txt] ");
   sMsg.append(new String(ComRecData.bRec));
   sMsg.append("\r\n");
   Slog.e(TAG, "receiver msg=" + sMsg);
}

然后新建端口,并打开

SerialControl com = new SerialControl();
com.setBaudRate(baudrate);
com.setPort("/dev/ttyS4");
openPort(com);
DispQueue = new DispQueueThread();
DispQueue.start();

三:在framework/base/service/core/jni 目录创建com_android_server_tnport_SerialPort.cpp

/*
 * Copyright 2009-2011 Cedric Priscal
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>

#include "android/log.h"

#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"

#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/vibrator.h>

#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>

static const char *TAG="serial_port";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)namespace android
{static int getBaudrate(jint baudrate){switch(baudrate) {case 0: return B0;
       case 50: return B50;
       case 75: return B75;
       case 110: return B110;
       case 134: return B134;
       case 150: return B150;
       case 200: return B200;
       case 300: return B300;
       case 600: return B600;
       case 1200: return B1200;
       case 1800: return B1800;
       case 2400: return B2400;
       case 4800: return B4800;
       case 9600: return B9600;
       case 19200: return B19200;
       case 38400: return B38400;
       case 57600: return B57600;
       case 115200: return B115200;
       case 230400: return B230400;
       case 460800: return B460800;
       case 500000: return B500000;
       case 576000: return B576000;
       case 921600: return B921600;
       case 1000000: return B1000000;
       case 1152000: return B1152000;
       case 1500000: return B1500000;
       case 2000000: return B2000000;
       case 2500000: return B2500000;
       case 3000000: return B3000000;
       case 3500000: return B3500000;
       case 4000000: return B4000000;
       default: return -1;
       }}static jobject android_server_tnport_SerialPort_open(JNIEnv *env, jclass thiz, jint baudrate, jint flags, jstring path){int fd;
       int speed;
       jobject mFileDescriptor;
       {speed = getBaudrate(baudrate);
          if (speed == -1) {/* TODO: throw an exception */
             LOGE("Invalid baudrate");
             return NULL;
          }}/* Opening device */
       {jboolean iscopy;
          const char *path_utf = env->GetStringUTFChars(path, &iscopy);
          LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
          fd = open(path_utf, O_RDWR | flags);
          LOGD("open() fd = %d", fd);
          env->ReleaseStringUTFChars(path, path_utf);
          if (fd == -1){LOGE("Cannot open port");
             return NULL;
          }}{struct termios cfg;
          LOGD("Configuring serial port");
          if (tcgetattr(fd, &cfg)){LOGE("tcgetattr() failed");
             close(fd);
             /* TODO: throw an exception */
             return NULL;
          }cfmakeraw(&cfg);
          cfsetispeed(&cfg, speed);
          cfsetospeed(&cfg, speed);

          if (tcsetattr(fd, TCSANOW, &cfg)){LOGE("tcsetattr() failed");
             close(fd);
             /* TODO: throw an exception */
             return NULL;
          }}{jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
          jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, "<init>", "()V");
          jfieldID descriptorID = env->GetFieldID(cFileDescriptor, "descriptor", "I");
          mFileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor);
          env->SetIntField(mFileDescriptor, descriptorID, (jint)fd);
       }return mFileDescriptor;
    }static void android_server_tnport_SerialPort_close(JNIEnv *env, jobject thiz){jclass SerialPortClass = env->GetObjectClass(thiz);
       jclass FileDescriptorClass = env->FindClass("java/io/FileDescriptor");

       jfieldID mFdID = env->GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
       jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I");

       jobject mFd = env->GetObjectField(thiz, mFdID);
       jint descriptor = env->GetIntField(mFd, descriptorID);

       LOGD("close(fd = %d)", descriptor);
       close(descriptor);
    }static const JNINativeMethod method_table[] = {{ "close", "()V", (void*)android_server_tnport_SerialPort_close},
        { "open", "(IILjava/lang/String;)Ljava/io/FileDescriptor;", (void*)android_server_tnport_SerialPort_open},
    };

    int register_android_server_tnport_SerialPort(JNIEnv *env){return jniRegisterNativeMethods(env, "com/android/server/tnport/SerialPort", method_table, NELEM(method_table));
    }}

并在当前目录下的Android.mk和onload.cpp文件中注册这个jni:

Android.mk

$(LOCAL_REL_DIR)/com_android_server_tnport_SerialPort.cpp \

onload.cpp

int register_android_server_tnport_SerialPort(JNIEnv* env);
register_android_server_tnport_SerialPort(env);

二:还有一种方法就是利用framework层本来提供了SerialPort和SerialManager两个类来操作串口:

安卓原始代码中的串口SerialPort SerialManger SerialService  ISerialService.aidl 等都是@hide隐藏的,外部应用无法调用,定制需要,所以将该Manager启用。下面记录下过程

1.启用SerialService

其掉这三个文件中的@hide标识:

frameworks/base/core/java/android/hardware/SerialManager.java

frameworks/base/core/java/android/hardware/SerialPort.java

frameworks/base/core/java/android/hardware/ISerialManager.aidl

SerialService文件位置:

frameworks/base/services/java/com/android/server/SerialService.java

该服务会在SystemSever.java中进行初始化,这里还需要将Context.java中的SERIAL_SERVICE的@hide去掉:

frameworks/base/core/java/android/content/Context.java

-    /*
-     * @hide
-     */
     public static final String SERIAL_SERVICE = "serial";

还有,   SerialService是通过读取R.array.config_serialPorts这个String array来加载的/dev/设备节点:

public SerialService(Context context) {
        mContext = context;
        mSerialPorts = context.getResources().getStringArray(
                com.android.internal.R.array.config_serialPorts);
    }

所以还需要添加下,文件位置:

framework/base/core/res/res/values/config.xml

在config_serialPorts中添加相应设备节点

<!-- List of paths to serial ports that are available to the serial manager.
         for example, /dev/ttyUSB0
    -->
    <string-array translatable="false" name="config_serialPorts">

<item>"/dev/ttyS0"</item>                     
    </string-array>

在使用时应用需要添加uses-permission权限 android.permission.SERIAL_PORT ;

2.安卓提供了个测试工具,在framework/base/tests/SerialChat, 可以进入该目录, 输入mm 进行编译,输出在 out/target/product/xxx/data/app/中,可以push到手机里验证。

3.权限授权问题,  我在测试这个SerialChat程序时发现android.permission.SERIAL_PORT并未被授权, 通过pm.checkPermission("android.permission.SERIAL_PORT", pinfo.packageName) 检查可以看到。

查看frameworks/base/core/res/AndroidManifest.xml中发现,该permission定义如下:

<!-- Allows applications to access serial ports via the SerialManager. -->
    <permission android:name="android.permission.SERIAL_PORT"
        android:label="@string/permlab_serialPort"
        android:description="@string/permdesc_serialPort"
        android:protectionLevel="signature|system" />

可以看到protectionLevel是signature|systeml;

signature表示当申请此权限的应用程序的签名与声明此权限的应用的签名相同时才会授权, 该应用是framwork-res.apk,使用的签名是platform,所以需要应用也要使用platform签名。

system表示是系统应用;

所以这里修改Android.mk,添加LOCAL_CERTIFICATE := platform 再重新编译

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := SerialChat
include $(BUILD_PACKAGE)

转载于:https://my.oschina.net/u/2542649/blog/1550254

android framework层添加串口操作相关推荐

  1. android power键测试,Android Framework层Power键关机流程(一,Power长按键操作处理)...

    一:Android处理Power按键长按操作 在Framework层中,Android4.x对Power键(KeyEvent.KEYCODE_POWER)的操作,我们从PhoneWindowManag ...

  2. android power 按键,Android Framework层Power键关机流程(一,Power长按键操作处理)

    一:Android处理Power按键长按操作 在Framework层中,Android4.x对Power键(KeyEvent.KEYCODE_POWER)的操作,我们从PhoneWindowManag ...

  3. 初识Android framework层

    Android系统的构成如下,从上到下依次是 Application应用层 Framework框架层 LIbrary系统库层 Linux内核层 关于Framework层: Android的Framew ...

  4. Android 系统的分区和文件系统(5)- Android Framework层上的工具和命令

    声明 Android系统中包含很多命令行工具,包括一些Linux继承来的工具,也有不少Android系统特有工具/命令,此篇介绍一些比较重要的工具/命令. 这些命令来自Android框架层(源码位置: ...

  5. android jni framework,Android Framework层的JNI机制(二)

    Java框架层中有很多地方使用JNI机制,每一个部分的框架层代码,都可能有与之对应的JNI库.先了解Java框架层的组成,继续看一下JNI在框架层中的使用. Java框架层的组成 Java框架层的实体 ...

  6. Android Framework层播放器评分机制

    本文涉及源码版本为:Oreo 8.0.0_r4 /frameworks/av/media/libmedia/mediaplayer.cpp /frameworks/av/include/media/m ...

  7. Android Framework层的蓝牙管理

    1.1 蓝牙技术简介 蓝牙(Bleuetooth)原是十世纪统一了丹麦的一个国王的名字,现取其"统一"的含义,用来意在统一无线局域网通讯的标准的蓝牙技术.蓝牙技 术是爱立信,IBM ...

  8. ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍

    从ActivityManagerService解读之Activity启动初探,到ActivityManagerService解读之Activity启动再探,到ActivityManagerServic ...

  9. Android Framework层Power键关机流程(二,关机流程)

    二,关机流程 从前一篇博文我们知道,当用户长按Power键时会弹出(关机.重启,飞行模式等选项)对话框,我们点击关机,则会弹出关机确认对话框.那么从选项对话框到关机确认对话框又是一个什么流程呢.下面我 ...

最新文章

  1. magento创建自定义页面 (Bestseller实例) Bestseller products in Magento
  2. ubuntu16.04下出现登陆不进去
  3. 前端传值后端接收不到_关于前端传参数,后台接收的问题
  4. ios Quartz 各种绘制图形用法
  5. 白化(Whitening): PCA白化 ZCA白化
  6. 从拉格朗日插值法到范德蒙行列式
  7. 当当网注册页面html源代码,当当网HTML源代码
  8. Cell:PopCOGenT鉴定微生物基因组间的基因流动
  9. linux开发板显示动态图片,开发板上显示图片
  10. Rancher Labs获2500万美元C轮融资
  11. 计算机硬件基础英语ppt,计算机英语 第一单元 计算机硬件 computer hardware.ppt
  12. 干货:esp32彩屏自制太空人主题透明手表!
  13. python大作业题目_Python大作业
  14. 接口获取行政区划代码_全国省市县行政区划分
  15. selenium模拟鼠标键盘(一)
  16. LeetCode/LintCode 题解丨一周爆刷字符串:独特的摩尔斯编码
  17. 点餐小程序系统/基于微信小程序的点餐系统/点餐平台网站
  18. c语言char s[] 语句,35、若有定义和语句: char s[10]=abcd;printf(%s\n,s); 则结果是(以下u代表空格)...
  19. 使用tesseract训练自己的字库提高识别率
  20. 经常生气的人,身体有什么变化?

热门文章

  1. 广东专插本计算机科学技术导论c程序设计,广东技术师范学院2018年专插本《计算机科学技术导论》考试大纲...
  2. oracle数据库连接自动关闭,oracle 自动关闭 数据库连接
  3. Linux下 ls 命令的高级用法8例
  4. 日语语音之清音、浊音、半浊音、长音、促音、拨音、拗音
  5. 复盘,你做到位了吗?
  6. C#_摄像头图像转换为Bitmap格式及绘制十字线
  7. asterisk配置文详解
  8. NP问题总结(概念+例子+证明)
  9. 在linux系统中查看组管理信息命令,Linux常用命令(五)账号和组管理
  10. 硅谷之行 (19) 硅谷的雨季