存档在 2013年3月

内存区划分

2013年3月19日

一. 在C中分为这几个存储区
1. – 由编译器自动分配释放;
2. – 一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收;
3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放
4.常量区,专门放常量的地方。- 程序结束释放

在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的”adgfdf”这样的字符串存放在常量区。比如: » 阅读更多: 内存区划分

用户态与核心态

2013年3月19日

386及以上的CPU实现了4个特权级模式(WINDOWS只用到了其中两个),其中特权级0(Ring0)是留给操作系统代码,设备驱动程序代码使用的,它们工作于系统核心态;而特权极3(Ring3)则给普通的用户程序使用,它们工作在用户态。运行于处理器核心态的代码不受任何的限制,可以自由地访问任何有效地址,进行直接端口访问。而运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permission Bitmap)中规定的可访问端口进行直接访问(此时处理器状态和控制标志寄存器EFLAGS中的IOPL通常为0,指明当前可以进行直接I/O的最低特权级别是Ring0)。以上的讨论只限于保护模式操作系统,象DOS这种实模式操作系统则没有这些概念,其中的所有代码都可被看作运行在核心态。既然运行在核心态有如此之多的优势,那么病毒当然没有理由不想得到Ring0。处理器模式从Ring3向Ring0的切换发生在控制权转移时,有以下两种情况:访问调用门的长转移指令CALL,访问中断门或陷阱门的INT指令。具体的转移细节由于涉及复杂的保护检查和堆栈切换,不再赘述,请参阅相关资料。现代的操作系统通常使用中断门来提供系统服务,通过执行一条陷入指令来完成模式切换,在INTEL X86上这条指令是INT,如在WIN9X下是INT30(保护模式回调),在LINUX下是INT80,在WINNT/2000下是INT2E。用户模式的服务程序(如系统DLL)通过执行一个INTXX来请求系统服务,然后处理器模式将切换到核心态,工作于核心态的相应的系统代码将服务于此次请求并将结果传给用户程序。

用户态又称目态,核心态又称管态
在X86下,可以理解成ring3 和 ring 0
用户态权限低,无权调用一些核心态才能调用的指令

这是两种内存保护态,一个进程4G地址空间中的每一页均被标记出它是否是处于核心态,所有系统地址空间中的页是核心态,用户空间中的页则为用户态。
访问标记为核心页的的唯一途径是运行在核心态,而只有操作系统和设备驱动才能运行在核心态。
因此一个应用程序不能使自己运行在核心态中,这样为应用程序和操作系统提供了内存保护的坚固级别。用户态怎么修改都不能让系统崩溃。当然应用程序可以通过加载设备驱动进入核心态,去修改系统数据。

[C++]Bridge设计模式

2013年3月19日

Bridge模式定义 :
将抽象和行为划分开来,各自独立,但能动态的结合。

任何事物对象都有抽象和行为之分,例如人,人是一种抽象,人分男人和女人等;人有行为,行为也有各种具体表现,所以,“人”与“人的行为”两个概念也反映了抽象和行为之分。

在面向对象设计的基本概念中,对象这个概念实际是由属性和行为两个部分组成的,属性我们可以认为是一种静止的,是一种抽象,一般情况下,行为是包含在一个对象中,但是,在有的情况下,我们需要将这些行为也进行归类,形成一个总的行为接口,这就是桥模式的用处。

为什么使用?
不希望抽象部分和行为有一种固定的绑定关系,而是应该可以动态联系的

如果一个抽象类或接口有多个具体实现(子类、concrete subclass),这些子类之间关系可能有以下两种情况:
1. 这多个子类之间概念是并列的,如举例,打桩,有两个实例:方形桩和圆形桩;这两个形状上的桩是并列的,没有概念上的重复。

2.这多个子类之中有内容概念上重叠.那么需要我们把抽象共同部分行为共同部分各自独立开来,原来是准备放在一个接口里,现在需要设计两个接口:抽象接口和行为接口,分别放置抽象和行为.

例如,一杯咖啡为例,子类实现类为四个:中杯加奶、大杯加奶、 中杯不加奶、大杯不加奶。

但是,我们注意到:上面四个子类中有概念重叠,可从另外一个角度进行考虑,这四个类实际是两个角色的组合:抽象 和行为,其中抽象为:中杯和大杯;行为为:加奶 不加奶(如加橙汁 加苹果汁).

实现四个子类在抽象和行为之间发生了固定的绑定关系,如果以后动态增加加葡萄汁的行为,就必须再增加两个类:中杯加葡萄汁和大杯加葡萄汁。显然混乱,扩展性极差。

那我们从分离抽象和行为的角度,使用Bridge模式来实现。

如何实现?
以上面提到的咖啡 为例. 我们原来打算只设计一个接口(抽象类),使用Bridge模式后,我们需要将抽象和行为分开,加奶和不加奶属于行为,我们将它们抽象成一个专门的行为接口.

抽象实例数为m,行为数为n,则可以进行m X n次组合。

来看看代码:
CoffeeImp.h

#ifndef _COFFEEIMP_H_
#define _COFFEEIMP_H_
class CoffeeImp
{
public:
	CoffeeImp();
	virtual ~CoffeeImp() = 0;
	virtual void Make();
};

class CoffeeImpSugar : public CoffeeImp
{
public:
	CoffeeImpSugar();
	~CoffeeImpSugar();
	void Make();
};

class CoffeeImpMilk : public CoffeeImp
{
public:
	CoffeeImpMilk();
	~CoffeeImpMilk();
	void Make();
};
#endif

CoffeeImp.cpp

#include "CoffeeImp.h"
#include
using namespace std;

CoffeeImp::CoffeeImp(){}
CoffeeImp::~CoffeeImp(){}
void CoffeeImp::Make(){}

CoffeeImpSugar::CoffeeImpSugar(){}
CoffeeImpSugar::~CoffeeImpSugar(){}
void CoffeeImpSugar::Make()
{
	cout<<"加糖"<<endl;
}

CoffeeImpMilk::CoffeeImpMilk(){}
CoffeeImpMilk::~CoffeeImpMilk(){}
void CoffeeImpMilk::Make()
{
	cout<<"加奶"<<endl;
}

Coffee.h

#ifndef _BRIDGE_H_
#define _BRIDGE_H_
class CoffeeImp;
class Coffee
{
public:
	Coffee();
	virtual ~Coffee() = 0;
	virtual void pourCoffee();
	void setCoffeeImp(CoffeeImp*);

protected:
	CoffeeImp *coffImp;
};

class BigCupCoffee : public Coffee
{
public:
	BigCupCoffee();
	~BigCupCoffee();
	void pourCoffee();
};

class SmallCupCoffee : public Coffee
{
public:
	SmallCupCoffee();
	~SmallCupCoffee();
	void pourCoffee();
};
#endif

Coffee.cpp

#include "Coffee.h"
#include "CoffeeImp.h"
#include "iostream"
using namespace std;

Coffee::Coffee(){}
Coffee::~Coffee(){}
void Coffee::setCoffeeImp(CoffeeImp *imp)
{
	coffImp = imp;
}
void Coffee::pourCoffee(){}

BigCupCoffee::BigCupCoffee(){cout<<"大杯";} 
BigCupCoffee::~BigCupCoffee(){} 
void BigCupCoffee::pourCoffee() 
{
 	coffImp->Make();
}
SmallCupCoffee::SmallCupCoffee(){cout<<"小杯";} 
SmallCupCoffee::~SmallCupCoffee(){} 
void SmallCupCoffee::pourCoffee() 
{
 	coffImp->Make();
}

main.cpp

#include "Coffee.h"
#include "CoffeeImp.h"

#include
using namespace std;

int main()
{
	CoffeeImp* coffeeImp = new CoffeeImpSugar();
	cout<<"Creater1想要:";
 	Coffee*	coffee = new BigCupCoffee(/*coffeeImp*/);
 	coffee->setCoffeeImp(coffeeImp);
	coffee->pourCoffee();
	delete coffeeImp, coffeeImp = 0;
	delete coffee, coffee = 0;
	cout<<"Creater2想要:";
 	coffeeImp = new CoffeeImpMilk();
 	coffee = new SmallCupCoffee();
 	coffee->setCoffeeImp(coffeeImp);
	coffee->pourCoffee();
	delete coffeeImp, coffeeImp = 0;
	delete coffee, coffee = 0;
	return 0;
}

结果为:

Creater1想要:大杯加糖
Creater2想要:小杯加奶

Factory Method与Abstract Factory

2013年3月18日

FactoryMethod是一个相对比较简单的创建型模式,但是能领悟或者用对的并不多见;很多示例都没有反应出Factory Method的核心思想,只是实现了一个简化版的Abstract Factory,然后给出的解释是Factory Method模式解决“单个对象”的需求变化,Abstract Factory 模式解决“系列对象”的需求变化。

试想一下,如果把1视为N的一种特殊情况,则一个产品系列可能只包含一个对象;那么我们是不是可以认为Factory Method是一个简化版的Abstract Factory呢?实际上,Factory Method模式与Abstract Factory模式虽然同属于对象创建型模式,并且AbstractFactory通常用Factory Method模式实现,并且效果上都可用于连接平行的类层次,但是这两个模式在思想上有着本质的区别。网上的文章抄来抄去,结果错误也被到处传。上一篇介绍了被普遍误用的Builder模式,这篇继续为Factory Method正名。 » 阅读更多: Factory Method与Abstract Factory

第一次带数电实验

2013年3月18日

时间:2013-03-18-15.55
地点:综合楼3楼
今天第一次去带数电实验,各种郁闷阿。N年以前学的东西已经遗忘,还好一起的老师比较和蔼。
只有加油,对任何一件事情都要付出百分百的努力!万事开头难!

[C++]单列设计模式

2013年3月18日

单列设计模式,顾名思义,只有一个对象存在。为了防止对象给构造或者被拷贝,需要将构造函数设置成private或者prrotected.将指向该类对象的指针作为静态数据成员,使用静态函数返回该指针。

Sigle.h

#ifndef _SIGLE_H_
#define _SIGLE_H_
class Sigle
{
private:
	Sigle();
	Sigle(const Sigle& t);
	Sigle& operator=(const Sigle& t);
	static Sigle* sigle;
public:
	static Sigle* getIntanse()
	{
		if(sigle == 0)
			return new Sigle();
	}
	~Sigle();
};
#endif

Sigle.cpp

#include "Sigle.h"
#include <iostream>
using namespace std;
Sigle* Sigle::sigle = 0;
Sigle::~Sigle() 
{
	if(sigle != 0)
		delete sigle, sigle = 0;
}
Sigle::Sigle()
{
	std::cout<<"构造函数"<<std::endl;
}

main.cpp

#include "Sigle.h"

int main()
{
	Sigle::getIntanse();
}

[C++]抽象工厂模式

2013年3月18日

前面说的简单工厂模式是针对于具有公有基类的情况。 如果多个产品类没有公共的基类呢。

抽象工厂模式关键就是将一组对象的创建封装到一个用于创建对象的工厂类中。

抽象工厂模式和简单工厂模式区别:抽象工厂模式用工厂类管理多个产品类对象创建,而简单工厂模式则用工厂类来管理一个产品类对象的创建。

AbstractFactory.h


#ifndef _DP_ABSTRACTFACTORY_H_
#define _DP_ABSTRACTFACTORY_H_

class ProductABase;
class ProductBBase;

class AbstractFactory
{
public:
 AbstractFactory();
 virtual ~AbstractFactory() = 0;
 virtual ProductABase* makeProductA();
 virtual ProductBBase* makeProductB();
};

class AbstractFactory1:public AbstractFactory
{
public:
 AbstractFactory1(){}
 ~AbstractFactory1(){}
 ProductABase* makeProductA();
 ProductBBase* makeProductB();
};
#endif

AbstractFactory.cpp


#include "Product.h"
#include "AbstractFactory.h"
AbstractFactory::AbstractFactory(){}
AbstractFactory::~AbstractFactory(){}

ProductABase*
AbstractFactory::makeProductA(){}
ProductBBase*
AbstractFactory::makeProductB(){}

ProductABase*
AbstractFactory1::makeProductA()
{
 return new ProductA();
}
ProductBBase*
AbstractFactory1::makeProductB()
{
 return new ProductB();
}

Product.h


#ifndef _PRODUCT_H_
#define _PRODUCT_H_
#include <iostream>
using namespace std;
class ProductABase
{
public:
 ProductABase();
 virtual ~ProductABase() = 0;
};
class ProductBBase
{
public:
 ProductBBase();
 virtual ~ProductBBase() = 0;
};

class ProductA : public ProductABase
{
public:
 ~ProductA(){}
 ProductA(){cout<<"A产品构造"<<endl;}
};
class ProductB : public ProductBBase
{
public:
 ~ProductB(){}
 ProductB(){cout<<"B产品构造"<<endl;}
};
#endif

Product.cpp


#include "Product.h"
ProductABase::ProductABase(){}
ProductABase::~ProductABase(){}
ProductBBase::ProductBBase(){}
ProductBBase::~ProductBBase(){}

main.cpp


#include "AbstractFactory.h"
#include "Product.h"

int main()
{

AbstractFactory* factory = new AbstractFactory1();
 ProductABase *productA = factory->makeProductA();
 ProductBBase *productB = factory->makeProductB();
 delete factory;
 delete productA;
 delete productB;
}

linux lsof命令详解

2013年3月18日

简介

lsof(list open files)是一个列出当前系统打开文件的工具。在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。

 

输出信息含义

在终端下输入lsof即可显示系统打开的文件,因为 lsof 需要访问核心内存和各种文件,所以必须以 root 用户的身份运行它才能够充分地发挥其功能。

直接输入lsof部分输出为:


COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
init 1 root cwd DIR 8,1 4096 2 /
init 1 root rtd DIR 8,1 4096 2 /
init 1 root txt REG 8,1 150584 654127 /sbin/init
udevd 415 root 0u CHR 1,3 0t0 6254 /dev/null
udevd 415 root 1u CHR 1,3 0t0 6254 /dev/null
udevd 415 root 2u CHR 1,3 0t0 6254 /dev/null
udevd 690 root mem REG 8,1 51736 302589 /lib/x86_64-linux-gnu/libnss_files-2.13.so
syslogd 1246 syslog 2w REG 8,1 10187 245418 /var/log/auth.log
syslogd 1246 syslog 3w REG 8,1 10118 245342 /var/log/syslog
dd 1271 root 0r REG 0,3 0 4026532038 /proc/kmsg
dd 1271 root 1w FIFO 0,15 0t0 409 /run/klogd/kmsg
dd 1271 root 2u CHR 1,3 0t0 6254 /dev/null

每行显示一个打开的文件,若不指定条件默认将显示所有进程打开的所有文件。

lsof输出各列信息的意义如下:

COMMAND:进程的名称 PID:进程标识符

USER:进程所有者

FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等 TYPE:文件类型,如DIR、REG等

DEVICE:指定磁盘的名称

SIZE:文件的大小

NODE:索引节点(文件在磁盘上的标识)

NAME:打开文件的确切名称
FD 列中的文件描述符cwd 值表示应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改,txt 类型的文件是程序代码,如应用程序二进制文件本身或共享库,如上列表中显示的 /sbin/init 程序。

其次数值表示应用程序的文件描述符,这是打开该文件时返回的一个整数。如上的最后一行文件/dev/initctl,其文件描述符为 10。u 表示该文件被打开并处于读取/写入模式,而不是只读 ® 或只写 (w) 模式。同时还有大写 的W 表示该应用程序具有对整个文件的写锁。该文件描述符用于确保每次只能打开一个应用程序实例。初始打开每个应用程序时,都具有三个文件描述符,从 0 到 2,分别表示标准输入、输出和错误流。所以大多数应用程序所打开的文件的 FD 都是从 3 开始。

与 FD 列相比,Type 列则比较直观。文件和目录分别称为 REG 和 DIR。而CHR 和 BLK,分别表示字符和块设备;或者 UNIX、FIFO 和 IPv4,分别表示 UNIX 域套接字、先进先出 (FIFO) 队列和网际协议 (IP) 套接字。

常用参数

lsof语法格式是:
lsof [options] filename


lsof abc.txt 显示开启文件abc.txt的进程
lsof -c abc 显示abc进程现在打开的文件
lsof -c -p 1234 列出进程号为1234的进程所打开的文件
lsof -g gid 显示归属gid的进程情况
lsof +d /usr/local/ 显示目录下被进程开启的文件
lsof +D /usr/local/ 同上,但是会搜索目录下的目录,时间较长
lsof -d 4 显示使用fd为4的进程
lsof -i 用以显示符合条件的进程情况
lsof -i[46] [protocol][@hostname|hostaddr][:service|port]
 --> IPv4 or IPv6
 protocol --> TCP or UDP
 hostname --> Internet host name
 hostaddr --> IPv4地址
 service --> /etc/service中的 service name (可以不止一个)
 port --> 端口号 (可以不止一个)

lsof使用实例

查找谁在使用文件系统
在卸载文件系统时,如果该文件系统中有任何打开的文件,操作通常将会失败。那么通过lsof可以找出那些进程在使用当前要卸载的文件系统,如下:
# lsof /GTES11/
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 4208 root cwd DIR 3,1 4096 2 /GTES11/
vim 4230 root cwd DIR 3,1 4096 2 /GTES11/
在这个示例中,用户root正在其/GTES11目录中进行一些操作。一个 bash是实例正在运行,并且它当前的目录为/GTES11,另一个则显示的是vim正在编辑/GTES11下的文件。要成功地卸载/GTES11,应该在通知用户以确保情况正常之后,中止这些进程。 这个示例说明了应用程序的当前工作目录非常重要,因为它仍保持着文件资源,并且可以防止文件系统被卸载。这就是为什么大部分守护进程(后台进程)将它们的目录更改为根目录、或服务特定的目录(如 sendmail 示例中的 /var/spool/mqueue)的原因,以避免该守护进程阻止卸载不相关的文件系统。

 

恢复删除的文件
当Linux计算机受到入侵时,常见的情况是日志文件被删除,以掩盖攻击者的踪迹。管理错误也可能导致意外删除重要的文件,比如在清理旧日志时,意外地删除了数据库的活动事务日志。有时可以通过lsof来恢复这些文件。
当进程打开了某个文件时,只要该进程保持打开该文件,即使将其删除,它依然存在于磁盘中。这意味着,进程并不知道文件已经被删除,它仍然可以向打开该文件时提供给它的文件描述符进行读取和写入。除了该进程之外,这个文件是不可见的,因为已经删除了其相应的目录索引节点。
在/proc 目录下,其中包含了反映内核和进程树的各种文件。/proc目录挂载的是在内存中所映射的一块区域,所以这些文件和目录并不存在于磁盘中,因此当我们对这些文件进行读取和写入时,实际上是在从内存中获取相关信息。大多数与 lsof 相关的信息都存储于以进程的 PID 命名的目录中,即 /proc/1234 中包含的是 PID 为 1234 的进程的信息。每个进程目录中存在着各种文件,它们可以使得应用程序简单地了解进程的内存空间、文件描述符列表、指向磁盘上的文件的符号链接和其他系统信息。lsof 程序使用该信息和其他关于内核内部状态的信息来产生其输出。所以lsof 可以显示进程的文件描述符和相关的文件名等信息。也就是我们通过访问进程的文件描述符可以找到该文件的相关信息。
当系统中的某个文件被意外地删除了,只要这个时候系统中还有进程正在访问该文件,那么我们就可以通过lsof从/proc目录下恢复该文件的内容。 假如由于误操作将/var/log/messages文件删除掉了,那么这时要将/var/log/messages文件恢复的方法如下:
首先使用lsof来查看当前是否有进程打开/var/logmessages文件,如下:
# lsof |grep /var/log/messages
syslogd 1283 root 2w REG 3,3 5381017 1773647 /var/log/messages (deleted)
从上面的信息可以看到 PID 1283(syslogd)打开文件的文件描述符为 2。同时还可以看到/var/log/messages已经标记被删除了。因此我们可以在 /proc/1283/fd/2 (fd下的每个以数字命名的文件表示进程对应的文件描述符)中查看相应的信息,如下:
# head -n 10 /proc/1283/fd/2
Aug 4 13:50:15 holmes86 syslogd 1.4.1: restart.
Aug 4 13:50:15 holmes86 kernel: klogd 1.4.1, log source = /proc/kmsg started.
Aug 4 13:50:15 holmes86 kernel: Linux version 2.6.22.1-8 (root@everestbuilder.linux-ren.org) (gcc version 4.2.0) #1 SMP Wed Jul 18 11:18:32 EDT 2007 Aug 4 13:50:15 holmes86 kernel: BIOS-provided physical RAM map: Aug 4 13:50:15 holmes86 kernel: BIOS-e820: 0000000000000000 – 000000000009f000 (usable) Aug 4 13:50:15 holmes86 kernel: BIOS-e820: 000000000009f000 – 00000000000a0000 (reserved) Aug 4 13:50:15 holmes86 kernel: BIOS-e820: 0000000000100000 – 000000001f7d3800 (usable) Aug 4 13:50:15 holmes86 kernel: BIOS-e820: 000000001f7d3800 – 0000000020000000 (reserved) Aug 4 13:50:15 holmes86 kernel: BIOS-e820: 00000000e0000000 – 00000000f0007000 (reserved) Aug 4 13:50:15 holmes86 kernel: BIOS-e820: 00000000f0008000 – 00000000f000c000 (reserved)
从上面的信息可以看出,查看 /proc/8663/fd/15 就可以得到所要恢复的数据。如果可以通过文件描述符查看相应的数据,那么就可以使用 I/O 重定向将其复制到文件中,如:
cat /proc/1283/fd/2 > /var/log/messages
对于许多应用程序,尤其是日志文件和数据库,这种恢复删除文件的方法非常有用。

 

实用命令


lsof `which httpd` //那个进程在使用apache的可执行文件
lsof /etc/passwd //那个进程在占用/etc/passwd
lsof /dev/hda6 //那个进程在占用hda6
lsof /dev/cdrom //那个进程在占用光驱
lsof -c sendmail //查看sendmail进程的文件使用情况
lsof -c courier -u ^zahn //显示出那些文件被以courier打头的进程打开,但是并不属于用户zahn
lsof -p 30297 //显示那些文件被pid为30297的进程打开
lsof -D /tmp 显示所有在/tmp文件夹中打开的instance和文件的进程。但是symbol文件并不在列

lsof -u1000 //查看uid是100的用户的进程的文件使用情况
lsof -utony //查看用户tony的进程的文件使用情况
lsof -u^tony //查看不是用户tony的进程的文件使用情况(^是取反的意思)
lsof -i //显示所有打开的端口
lsof -i:80 //显示所有打开80端口的进程
lsof -i -U //显示所有打开的端口和UNIX domain文件
lsof -i UDP@[url]www.akadia.com:123 //显示那些进程打开了到www.akadia.com的UDP的123(ntp)端口的链接
lsof -i tcp@ohaha.ks.edu.tw:ftp -r //不断查看目前ftp连接的情况(-r,lsof会永远不断的执行,直到收到中断信号,+r,lsof会一直执行,直到没有档案被显示,缺省是15s刷新)
lsof -i tcp@ohaha.ks.edu.tw:ftp -n //lsof -n 不将IP转换为hostname,缺省是不加上-n参数