入门—七.实用工具

1.简介


UTIL模块的基本介绍

util模块呢,是一个Node.js核心模块,提供常用函数的集合,用于弥补核心JavaScript的一些功能过于精简的不足。并且还提供了一系列常用工具,用来对数据的输出和验证。

2.转换字符串

inspect函数的基本用法

util.inspect(object,[showHidden],[depth],[colors])是一个将任意对象转换为字符串的函数,通常用于调试和错误输出。它至少接受一个参数object,即要转换的对象,我们来学习它的简单用法。使用语法如下:

1
2
3
var util = require('util');
var result = util.inspect(Object);
console.log(result);

运行结果:

1
[Function: Object]

3.字符串格式化


format函数的基本用法

format函数根据第一个参数,返回一个格式化字符串,第一个参数是一个可包含零个或多个占位符的字符串。每一个占位符被替换为与其对应的转换后的值,支持的占位符有:”%s(字符串)”、”%d(数字<整型和浮点型>)”、”%j(JSON)”、”%(单独一个百分号则不作为一个参数)”。

1:如果占位符没有相对应的参数,占位符将不会被替换.如示例:

1
2
3
var util = require('util');
var result = util.format('%s:%s', 'foo');
console.log(result);

运行结果:

1
'foo:%s'

2:如果有多个参数占位符,额外的参数将会调用util.inspect()转换为字符串。这些字符串被连接在一起,并且以空格分隔。如示例:

1
2
3
var util = require('util');
var result = util.format('%s:%s', 'foo', 'bar', 'baz');
console.log(result);

运行结果:

1
'foo:bar baz'

3:如果第一个参数是一个非格式化字符串,则会把所有的参数转成字符串并以空格隔开拼接在一块,而且返回该字符串。如示例:

1
2
3
var util = require('util');
var result = util.format(1, 2, 3);
console.log(result);

运行结果:

1
'1 2 3'

4.数组验证


isArray函数的基本用法

isArray函数可以判断对象是否为数组类型,是则返回ture,否则为false。语法如下:

1
2
3
var util = require('util');
var result = util.isArray([]);
console.log(result);

运行结果:

1
true

5.日期验证


isDate函数的基本用法

isDate函数可以判断对象是否为日期类型,是则返回ture,否则返回false。语法如下:

例1:querystring.parse(“字符串”,”分隔符”,”分配符”)

1
2
3
var util = require('util');
var result = util.isDate(new Date());
console.log(result);

运行结果:

1
true

6.正则验证


isRegExp函数的基本用法

isRegExp函数可以判断对象是否为正则类型,是则返回ture,否则返回false。语法如下:

1
2
3
var util = require('util');
var result = util.isRegExp(Object);
console.log(result);

请自行验证。

入门—六.字符串转换

1.简介


Query String模块的基本用法

Query String模块用于实现URL参数字符串与参数对象之间的互相转换,提供了”stringify”、”parse”等一些实用函数来针对字符串进行处理,通过序列化和反序列化,来更好的应对实际开发中的条件需求,对于逻辑的处理也提供了很好的帮助。

2.序列化


stringify函数的基础用法

stringify函数的作用就是序列化对象,也就是说将对象类型转换成一个字符串类型(默认的分割符(”&”)和分配符(”=”))。

例1:querystring.stringify(“对象”)

1
2
3
var querystring= require('querystring');
var result = querystring.stringify({foo:'bar',cool:['xux', 'yys']});
console.log(result);

运行结果:

1
foo=bar&cool=xux&cool=yys

3.序列化<多参数>


stringify函数的多参数用法

stringify函数的多参数用法,上节我们知道了对象被序列化为字符串之后默认是通过分割符(”&”)和分配符(”=”)组成的,那可不可以改变呢,这节我们就来了解一下,是否可以自己去定义组合结果,看下面的小例子

例1:querystring.stringify(“对象”,”分隔符”,”分配符”)

1
2
3
var querystring = require('querystring');
var result = querystring.stringify({foo:'bar',cool:['xux', 'yys']},'*','$');
console.log(result);

运行结果:

1
'foo$bar*cool$xux*cool$yys'

4.反序列化


parse函数的基本用法

接下来就来学习反序列化函数——parse函数,parse函数的作用就是反序列化字符串(默认是由”=”、”&”拼接而成),转换得到一个对象类型。如下示例:

例1:querystring.parse(“字符串”)

1
2
3
var querystring = require('querystring');
var result = querystring.parse('foo=bar&cool=xux&cool=yys');
console.log(result);

运行结果:

1
{ foo: 'bar', cool: ['xux', 'yys']}

5.反序列化<多参数>


parse函数的多参数用法

和上节stringify函数的多参数用法不同的是,parse函数可以根据用户所自定义的分割符、分配符来反序列化字符串,从而得到相应的对象结果.如下示例:

例1:querystring.parse(“字符串”,”分隔符”,”分配符”)

1
2
3
var querystring = require('querystring');
var result = querystring.parse('foo@bar$cool@xux$cool@yys','@','$');
console.log(result);

运行结果:

1
{ foo: '', bar: 'cool', xux: 'cool', yys: '' }

入门—五.path优化

1.简介


path模块的基本用法

本模块包含一套用于处理和转换文件路径的工具集,用于处理目录的对象,提高用户开发效率.

2.格式化路径


normalize函数的基础用法

normalize函数将不符合规范的路径经过格式化转换为标准路径,解析路径中的.与..外,还能去掉多余的斜杠。
如下示例:

1
2
3
var path = require('path');  
var data = path.normalize('/path///normalize/hi/..');
console.log(data);

运行结果:

1
'/path/normalize/'

3.组合路径


join函数的基本用法

join函数将传入的多个路径拼接为标准路径并将其格式化,返回规范后的路径,避免手工拼接路径字符串的繁琐. 如下示例:

1
2
3
var path = require('path');
var data = path.join('///you', '/are', '//beautiful');
console.log(data);

运行结果:

1
'/you/are/beautiful'

4.dirname


dirname函数的基本用法

dirname函数用来返回路径中的目录名. 如下示例:

1
2
3
var path = require('path');
var data = path.dirname('/foo/strong/cool/nice');
console.log(data);

运行结果:

1
'/foo/strong/cool'

5.basename


basename函数的基础用法

basename函数可返回路径中的最后一部分,并且可以对其进行条件排除. 如下示例:

例1:path.basename(‘路径字符串’);

例2:path.basename(‘路径字符串’, ‘[ext]’)<排除[ext]后缀字符串>;

1
2
3
4
var path = require('path');    
var data1 = path.basename('/foo/strong/basename/index.html');
var data2 = path.basename('/foo/strong/basename/index.html','.html');
console.log(data1 + ' "and" ' + data2);

运行结果:

1
'index.html "and" index'

6.extname


extname函数的基础用法

extname函数返回路径中文件的扩展名(以最后一个’.’开始,返回’.’以及’.’以后的所有字符串,如没有’.’,则返回空字符串). 如下示例:

1
2
3
var path = require('path');
var data = path.extname('index.html');
console.log(data);

运行结果:

1
'.html'

入门—四.url处理

1.简介


url模块的基本用法

node.js为互联网而生,和url打交道是无法避免的了,url模块提供一些基础的url处理。

2.parse


parse函数的基础用法

parse函数的作用是解析url,返回一个json格式的数组,请看如下示例:

1
2
var url = require('url');
url.parse('http://www.baidu.com');

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
{ protocol: 'http:',
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: null,
pathname: 'www.baidu.com',
path: 'www.baidu.com',
href: 'http://www.baidu.com'
}

3.条件解析


parse函数 —— 条件解析

parse函数的第二个参数是布尔类型,当参数为true时,会将查询条件也解析成json格式的对象。

1
2
var url = require('url');
url.parse('http://www.baidu.com?page=1',true);

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
{ protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com',
port: null,
hostname: 'www.baidu.com',
hash: null,
search: '?page=1',
query: { page: '1' },
pathname: '/',
path: '/?page=1',
href: 'http://www.baidu.com/?page=1'
}

注意query值的不同

4.解析主机


parse函数 —— 解析主机

parse函数的第三个参数也是布尔类型的,当参数为true,解析时会将url的”//“和第一个”/“之间的部分解析为主机名,示例如下:

1
2
var url = require('url');
url.parse('http://www.baidu.com/news',false,true);

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
{ protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com',
port: null,
hostname: 'www.baidu.com',
hash: null,
search: null,
query: null,
pathname: '/news',
path: '/news',
href: 'http://www.baidu.com/news'
}

5.格式化


format函数的基础用法

format函数的作用与parse相反,它的参数是一个JSON对象,返回一个组装好的url地址,请看如下示例:

1
2
3
4
5
6
7
8
var url = require('url');
url.format({
protocol: 'http:',
hostname:'www.baidu.com',
port:'80',
pathname :'/news',
query:{page:1}
});

运行结果:

1
http://www.baidu.com/news?page=1

6.reslove


resolve函数的基础用法

resolve函数的参数是两个路径,第一个路径是开始的路径或者说当前路径,第二个则是想要去往的路径,返回值是一个组装好的url,示例如下:

1
2
3
var url = require('url');
url.resolve('http://example.com/', '/one') // 'http://example.com/one'
url.resolve('http://example.com/one', '/two') // 'http://example.com/two'

入门—三.文件I/O

1.简介


fs模块的基本用法

开发中我们经常会有文件I/O的需求,node.js中提供一个名为fs的模块来支持I/O操作,fs模块的文件I/O是对标准POSIX函数的简单封装。

2.写入文件


writeFile函数的基本用法

文件I/O,写入是必修课之一。fs模块提供writeFile函数,可以异步的将数据写入一个文件, 如果文件已经存在则会被替换。用法如下:

例:fs.writeFile(filename, data, callback)

1
2
3
4
5
var fs= require("fs");
fs.writeFile('test.txt', 'Hello Node', function (err) {
if (err) throw err;
console.log('Saved successfully'); //文件被保存
});

数据参数可以是string或者是Buffer,编码格式参数可选,默认为”utf8”,回调函数只有一个参数err。

3.追加文件


appendFile函数的基本用法

writeFile函数虽然可以写入文件,但是如果文件已经存在,我们只是想添加一部分内容,它就不能满足我们的需求了,很幸运,fs模块中还有appendFile函数,它可以将新的内容追加到已有的文件中,如果文件不存在,则会创建一个新的文件。使用方法如下:

例:fs.appendFile(文件名,数据,编码,回调函数(err));

1
2
3
4
5
6
var fs= require("fs"); 
fs.appendFile('test.txt', 'data to append', function (err) {
if (err) throw err;
//数据被添加到文件的尾部
console.log('The "data to append" was appended to file!');
});

编码格式默认为”utf8”

4.是否存在


exists函数的基本用法

如何检查一个文件是否存在呢?我想exists函数可以帮助你,用法如下:

例:fs.exists(文件,回调函数(exists));

exists的回调函数只有一个参数,类型为布尔型,通过它来表示文件是否存在。

1
2
3
4
var fs= require("fs");
fs.exists('/etc/passwd', function (exists) {
console.log(exists ? "存在" : "不存在!");
});

5.修改名称


rename函数的基本用法

修改文件名称是我们经常会遇见的事情,rename函数提供修改名称服务:

1
2
3
4
5
var fs= require("fs");
fs.rename(旧文件,新文件,回调函数(err){
if (err) throw err;
console.log('Successful modification,');
});

6.移动文件


移动文件也是我们经常会遇见的,可是fs没有专门移动文件的函数,但是我们可以通过rename函数来达到移动文件的目的,示例如下。

1
2
3
4
5
var fs = require('fs');
fs.rename(oldPath,newPath,function (err) {
if (err) throw err;
console.log('renamed complete');
});

7.读取文件


readFile函数的基本用法

读取文件是最常用到的功能之一,使用fs模块读取文件语法如下:

例:fs.readFile(文件,编码,回调函数);

1
2
3
4
5
var fs = require('fs');
fs.readFile(文件名, function (err, data) {
if (err) throw err;
console.log(data);
});

回调函数里面的data,就是读取的文件内容。

8.删除文件


unlink函数的基本用法

面对一堆垃圾的文件总是有想删除的冲动,我有强迫症?你才有呢。
好在有unlink函数,终于得救了,示例如下:
例:fs.unlink(文件,回调函数(err));

1
2
3
4
5
var fs = require('fs');
fs.unlink(文件, function(err) {
if (err) throw err;
console.log('successfully deleted');
});

9.创建目录


mkdir函数的基本用法

除了针对文件的操作,目录的创建、删除也经常遇到的,下面我们来看看node.js中如何创建目录:

1
fs.mkdir(路径,权限,回调函数(err));

参数

1.路径:新创建的目录。
2.权限:可选参数,只在linux下有效,表示目录的权限,默认为0777,表示文件所有者、文件所有者所在的组的用户、所有用户,都有权限进行读、写、执行的操作。
3.回调函数:当发生错误时,错误信息会传递给回调函数的err参数。

10.删除目录


rmdir函数的基本用法

删除目录也是必不可少的功能,rmdir函数可以删除指定的目录:

例:fs.rmdir(路径,回调函数(err));

1
2
3
4
5
var fs = require('fs'); 
fs.rmdir(path, function(err) {
if (err) throw err;
console.log('ok');
});

11.读取目录


readdir函数的基本用法

如果要读取目录下所有的文件应该怎么办呢?readdir函数可以读取到指定目录下所有的文件,示例如下:

1
2
var fs = require('fs');
fs.readdir(目录,回调函数(err,files));

回调函数 (callback) 接受两个参数 (err, files) 其中 files 是一个存储目录中所包含的文件名称的数组,数组中不包括 ‘.’ 和 ‘..’。

12.小结


文件I/O是最基本的操作,应该熟悉掌握。

fs模块不但提供异步的文件操作,还提供相应的同步操作方法,需要指出的是,nodejs采用异步I/O正是为了避免I/O时的等待时间,提高CPU的利用率,所以在选择使用异步或同步方法的时候需要权衡取舍。

入门—二.进程管理

1.简介


process是一个全局内置对象,可以在代码中的任何位置访问此对象,这个对象代表我们的node.js代码宿主的操作系统进程对象。

使用process对象可以截获进程的异常、退出等事件,也可以获取进程的当前目录、环境变量、内存占用等信息,还可以执行进程退出、工作目录切换等操作。

process对象的一些常用方法。

2.cwd


当我们想要查看应用程序当前目录时,可以使用cwd函数,使用语法如下:

1
process.cwd();

3.chdir


如果需要改变应用程序目录,就要使用chdir函数了,它的用法如下:

1
process.chdir("目录");

4.stdout


stdout是标准输出流,它是干什么的呢?请下看下面的示例:

1
2
3
console.log = function(d){
process.stdout.write(d+'\n');
}

没错,它的作用就是将内容打印到输出设备上,console.log就是封装了它。

5.stderr


stderr是标准错误流,和stdout的作用差不多,不同的是它是用来打印错误信息的,我们可以通过它来捕获错误信息,基本使用方法如下:

1
process.stderr.write(输入内容);

6.stdin


stdin是进程的输入流,我们可以通过注册事件的方式来获取输入的内容,如下:

1
2
3
4
5
6
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write('data: ' + chunk);
}
});

示例中的chunk就是输入流中的内容。

7.exit


如果你需要在程序内杀死进程,退出程序,可以使用exit函数,示例如下:

1
process.exit(code);

参数code为退出后返回的代码,如果省略则默认返回0;

8.注册事件


前面讲到如何在输入流中打印信息,当我们需要获取stdout内容的时候应该怎么做呢?请看如下的示例:

1
2
3
process.stdout.on('data',function(data){
console.log(data);
});

为stdout注册data事件,我们就可以拿到它输出的内容了。

9.设置编码


在我们的输入输出的内容中有中文的时候,可能会乱码的问题,这是因为编码不同造成的,所以在这种情况下需要为流设置编码,如下示例:

1
2
3
process.stdin.setEncoding(编码);
process.stdout.setEncoding(编码);
process.stderr.setEncoding(编码);

常用的编码格式有”utf8”等

入门—一.快速入门

1.hello world


node使用javascript作为开发语言。没错,就是通常我们在前端页面里使用的javascript!如:

1
console.log('hello world');

2.回调函数


由于node是一个异步事件驱动的平台,所以在代码中我们经常需要使用回调函数。下面是回调函数应用的经典示例:

1
2
3
setTimeout(function(){
console.log('callback is called');
},2000);

3.标准回调函数


node.js中回调函数格式是约定俗成的,它有两个参数,第一个参数为err,第二个参数为data,顾名思义,err是错误信息,data则是返回的数据,示例如下:

1
2
3
function(err,data){

}

4.获取模块


为了支持快速开发,node平台上提供了大量的模块,封装了各自不同的功能,那么我们将如何调获取想要的模块呢, 在node中,我们可以使用require函数,具体语法如下:

1
require("模块");

通过require函数我们就可以获取相应模块进而使用它的任意功能了。

5.使用模块


os模块可提供操作系统的一些基本信息,它的一些常用方法如下:

1
2
3
4
5
6
var os = require("os");
var result = os.platform(); //查看操作系统平台
//os.release(); 查看操作系统版本
//os.type(); 查看操作系统名称
//os.arch(); 查看操作系统CPU架构
console.log(result);

POSIX

POSIX (Portable Operating System Interface) is a set of standard operating systeminterfaces based on the Unix operating system. The need for standardization arose because enterprises using computers wanted to be able to develop programs that could be moved among different manufacturer’s computer systems without having to be recoded. Unix was selected as the basis for a standard system interface partly because it was “manufacturer-neutral.” However, several major versions of Unix existed so there was a need to develop a common denominator system.

Informally, each standard in the POSIX set is defined by a decimal following the POSIX. Thus, POSIX.1 is the standard for an application program interface in the C language. POSIX.2 is the standard shell and utility interface (that is to say, the user’s command interface with the operating system). These are the main two interfaces, but additional interfaces, such as POSIX.4 for thread management, have been developed or are being developed. The POSIX interfaces were developed under the auspices of the Institute of Electrical and Electronics Engineers (IEEE).

POSIX.1 and POSIX.2 interfaces are included in a somewhat larger interface known as the X/Open Programming Guide (also known as the “Single UNIX Specification” and “UNIX 03“). The Open Group, an industry standards group, owns the UNIX trademark and can thus “brand” operating systems that conform to the interface as “UNIX” systems. IBM’s OS/390 is an example of an operating system that includes a branded UNIX interface. (Note that the trademark is “UNIX”; the generic terms for these operating systems is “Unix.”)

通过ulimit改善系统性能

概述

系统性能一直是一个受关注的话题,如何通过最简单的设置来实现最有效的性能调优,如何在有限资源的条件下保证程序的运作,ulimit 是我们在处理这些问题时,经常使用的一种简单手段。ulimit 是一种 linux 系统的内键功能,它具有一套参数集,用于为由它生成的 shell 进程及其子进程的资源使用设置限制。本文将在后面的章节中详细说明 ulimit 的功能,使用以及它的影响,并以具体的例子来详细地阐述它在限制资源使用方面的影响。

ulimit 的功能和用法

ulimit 功能简述

假设有这样一种情况,当一台 Linux 主机上同时登陆了 10 个人,在系统资源无限制的情况下,这 10 个用户同时打开了 500 个文档,而假设每个文档的大小有 10M,这时系统的内存资源就会受到巨大的挑战。

而实际应用的环境要比这种假设复杂的多,例如在一个嵌入式开发环境中,各方面的资源都是非常紧缺的,对于开启文件描述符的数量,分配堆栈的大小,CPU 时间,虚拟内存大小,等等,都有非常严格的要求。资源的合理限制和分配,不仅仅是保证系统可用性的必要条件,也与系统上软件运行的性能有着密不可分的联系。这时,ulimit 可以起到很大的作用,它是一种简单并且有效的实现资源限制的方式。

ulimit 用于限制 shell 启动进程所占用的资源,支持以下各种类型的限制:所创建的内核文件的大小、进程数据块的大小、Shell 进程创建文件的大小、内存锁住的大小、常驻内存集的大小、打开文件描述符的数量、分配堆栈的最大大小、CPU 时间、单个用户的最大线程数、Shell 进程所能使用的最大虚拟内存。同时,它支持硬资源和软资源的限制。

作为临时限制,ulimit 可以作用于通过使用其命令登录的 shell 会话,在会话终止时便结束限制,并不影响于其他 shell 会话。而对于长期的固定限制,ulimit 命令语句又可以被添加到由登录 shell 读取的文件中,作用于特定的 shell 用户。

图 1. ulimit 的使用


在下面的章节中,将详细介绍如何使用 ulimit 做相应的资源限制。

如何使用 ulimit

ulimit 通过一些参数选项来管理不同种类的系统资源。在本节,我们将讲解这些参数的使用。

ulimit 命令的格式为:ulimit [options] [limit]

具体的 options 含义以及简单示例可以参考以下表格。

表 1. ulimit 参数说明
选项 [options] 含义 例子
-H 设置硬资源限制,一旦设置不能增加。 ulimit – Hs 64;限制硬资源,线程栈大小为 64K。
-S 设置软资源限制,设置后可以增加,但是不能超过硬资源设置。 ulimit – Sn 32;限制软资源,32 个文件描述符。
-a 显示当前所有的 limit 信息。 ulimit – a;显示当前所有的 limit 信息。
-c 最大的 core 文件的大小, 以 blocks 为单位。 ulimit – c unlimited; 对生成的 core 文件的大小不进行限制。
-d 进程最大的数据段的大小,以 Kbytes 为单位。 ulimit -d unlimited;对进程的数据段大小不进行限制。
-f 进程可以创建文件的最大值,以 blocks 为单位。 ulimit – f 2048;限制进程可以创建的最大文件大小为 2048 blocks。
-l 最大可加锁内存大小,以 Kbytes 为单位。 ulimit – l 32;限制最大可加锁内存大小为 32 Kbytes。
-m 最大内存大小,以 Kbytes 为单位。 ulimit – m unlimited;对最大内存不进行限制。
-n 可以打开最大文件描述符的数量。 ulimit – n 128;限制最大可以使用 128 个文件描述符。
-p 管道缓冲区的大小,以 Kbytes 为单位。 ulimit – p 512;限制管道缓冲区的大小为 512 Kbytes。
-s 线程栈大小,以 Kbytes 为单位。 ulimit – s 512;限制线程栈的大小为 512 Kbytes。
-t 最大的 CPU 占用时间,以秒为单位。 ulimit – t unlimited;对最大的 CPU 占用时间不进行限制。
-u 用户最大可用的进程数。 ulimit – u 64;限制用户最多可以使用 64 个进程。
-v 进程最大可用的虚拟内存,以 Kbytes 为单位。 ulimit – v 200000;限制最大可用的虚拟内存为 200000 Kbytes。

我们可以通过以下几种方式来使用 ulimit:

  • 在用户的启动脚本中

    如果用户使用的是 bash,就可以在用户的目录下的 .bashrc 文件中,加入 ulimit – u 64,来限制用户最多可以使用 64 个进程。此外,可以在与 .bashrc 功能相当的启动脚本中加入 ulimt。

  • 在应用程序的启动脚本中

    如果用户要对某个应用程序 myapp 进行限制,可以写一个简单的脚本 startmyapp。

    ulimit – s 512 `myapp`

    以后只要通过脚本 startmyapp 来启动应用程序,就可以限制应用程序 myapp 的线程栈大小为 512K。

  • 直接在控制台输入

    user@tc511-ui:~>ulimit – p 256

    限制管道的缓冲区为 256K。

用户进程的有效范围

ulimit 作为对资源使用限制的一种工作,是有其作用范围的。那么,它限制的对象是单个用户,单个进程,还是整个系统呢?事实上,ulimit 限制的是当前 shell 进程以及其派生的子进程。举例来说,如果用户同时运行了两个 shell 终端进程,只在其中一个环境中执行了 ulimit – s 100,则该 shell 进程里创建文件的大小收到相应的限制,而同时另一个 shell 终端包括其上运行的子程序都不会受其影响:

Shell 进程 1

ulimit – s 100 `cat testFile > newFile File size limit exceeded`

Shell 进程 2

cat testFile > newFile `ls – s newFile 323669 newFile`

那么,是否有针对某个具体用户的资源加以限制的方法呢?答案是有的,方法是通过修改系统的 /etc/security/limits 配置文件。该文件不仅能限制指定用户的资源使用,还能限制指定组的资源使用。该文件的每一行都是对限定的一个描述,格式如下:

<`domain> <type> <item> <value>`

domain 表示用户或者组的名字,还可以使用 * 作为通配符。Type 可以有两个值,soft 和 hard。Item 则表示需要限定的资源,可以有很多候选值,如 stack,cpu,nofile 等等,分别表示最大的堆栈大小,占用的 cpu 时间,以及打开的文件数。通过添加对应的一行描述,则可以产生相应的限制。例如:

* hard noflle 100

该行配置语句限定了任意用户所能创建的最大文件数是 100。

现在已经可以对进程和用户分别做资源限制了,看似已经足够了,其实不然。很多应用需要对整个系统的资源使用做一个总的限制,这时候我们需要修改 /proc 下的配置文件。/proc 目录下包含了很多系统当前状态的参数,例如 /proc/sys/kernel/pid_max,/proc/sys/net/ipv4/ip_local_port_range 等等,从文件的名字大致可以猜出所限制的资源种类。由于该目录下涉及的文件众多,在此不一一介绍。有兴趣的读者可打开其中的相关文件查阅说明。

ulimit 管理系统资源的例子

ulimit 提供了在 shell 进程中限制系统资源的功能。本章列举了一些使用 ulimit 对用户进程进行限制的例子,详述了这些限制行为以及对应的影响,以此来说明 ulimit 如何对系统资源进行限制,从而达到调节系统性能的功能。

使用 ulimit 限制 shell 的内存使用

在这一小节里向读者展示如何使用 – d,– m 和 – v 选项来对 shell 所使用的内存进行限制。

首先我们来看一下不设置 ulimit 限制时调用 ls 命令的情况:

图 2. 未设置 ulimit 时 ls 命令使用情况


大家可以看到此时的 ls 命令运行正常。下面设置 ulimit:

>ulimit -d 1000 -m 1000 -v 1000

这里再温习一下前面章节里介绍过的这三个选项的含义:

-d:设置数据段的最大值。单位:KB。

-m:设置可以使用的常驻内存的最大值。单位:KB。

-v:设置虚拟内存的最大值。单位:KB。

通过上面的 ulimit 设置我们已经把当前 shell 所能使用的最大内存限制在 1000KB 以下。接下来我们看看这时运行 ls 命令会得到什么样的结果:

haohe@sles10-hehao:~/code/ulimit> ls test -l `/bin/ls: error while loading shared libraries: libc.so.6: failed to map segment from shared object: Cannot allocate memory`

从上面的结果可以看到,此时 ls 运行失败。根据系统给出的错误信息我们可以看出是由于调用 libc 库时内存分配失败而导致的 ls 出错。那么我们来看一下这个 libc 库文件到底有多大:

图 3. 查看 libc 文件大小


从上面的信息可以看出,这个 libc 库文件的大小是 1.5MB。而我们用 ulimit 所设置的内存使用上限是 1000KB,小于 1.5MB,这也就充分证明了 ulimit 所起到的限制 shell 内存使用的功能。

使用 ulimit 限制 shell 创建的文件的大小

接下来向读者展示如何使用 -f 选项来对 shell 所能创建的文件大小进行限制。

首先我们来看一下,没有设置 ulimit -f 时的情况:

图 4. 查看文件


现有一个文件 testFile 大小为 323669 bytes,现在使用 cat 命令来创建一个 testFile 的 copy:

图 5. 未设置 ulimit 时创建复本


从上面的输出可以看出,我们成功的创建了 testFile 的拷贝 newFile。

下面我们设置 ulimt – f 100:

> ulimit -f 100

-f 选项的含义是:用来设置 shell 可以创建的文件的最大值。单位是 blocks。

现在我们再来执行一次相同的拷贝命令看看会是什么结果:

图 6. 设置 ulimit 时创建复本


这次创建 testFile 的拷贝失败了,系统给出的出错信息时文件大小超出了限制。在 Linux 系统下一个 block 的默认大小是 512 bytes。所以上面的 ulimit 的含义就是限制 shell 所能创建的文件最大值为 512 x 100 = 51200 bytes,小于 323669 bytes,所以创建文件失败,符合我们的期望。这个例子说明了如何使用 ulimit 来控制 shell 所能创建的最大文件。

使用 ulimit 限制程序所能创建的 socket 数量

考虑一个现实中的实际需求。对于一个 C/S 模型中的 server 程序来说,它会为多个 client 程序请求创建多个 socket 端口给与响应。如果恰好有大量的 client 同时向 server 发出请求,那么此时 server 就会需要创建大量的 socket 连接。但在一个系统当中,往往需要限制单个 server 程序所能使用的最大 socket 数,以供其他的 server 程序所使用。那么我们如何来做到这一点呢?答案是我们可以通过 ulimit 来实现!细心的读者可能会发现,通过前面章节的介绍似乎没有限制 socket 使用的 ulimit 选项。是的,ulimit 并没有哪个选项直接说是用来限制 socket 的数量的。但是,我们有 -n 这个选项,它是用于限制一个进程所能打开的文件描述符的最大值。在 Linux 下一切资源皆文件,普通文件是文件,磁盘打印机是文件,socket 当然也是文件。在 Linux 下创建一个新的 socket 连接,实际上就是创建一个新的文件描述符。如下图所示(查看某个进程当前打开的文件描述符信息):

图 7. 查看进程打开文件描述符


因此,我们可以通过使用 ulimit – n 来限制程序所能打开的最大文件描述符数量,从而达到限制 socket 创建的数量。

使用 ulimit 限制 shell 多线程程序堆栈的大小(增加可用线程数量)

在最后一个例子中,向大家介绍如何使用 -s(单位 KB)来对线程的堆栈大小进行限制,从而减少整个多线程程序的内存使用,增加可用线程的数量。这个例子取自于一个真实的案例。我们所遇到的问题是系统对我们的多线程程序有如下的限制:

ulimit -v 200000

根据本文前面的介绍,这意味着我们的程序最多只能使用不到 200MB 的虚拟内存。由于我们的程序是一个多线程程序,程序在运行时会根据需要创建新的线程,这势必会增加总的内存需求量。一开始我们对堆栈大小的限制是 1024 (本例子中使用 1232 来说明):

# ulimit – s 1232

当我们的程序启动后,通过 pmap 来查看其内存使用情况,可以看到多个占用 1232KB 的数据段,这些就是程序所创建的线程所使用的堆栈:

图 8. 程序线程所使用的堆栈


每当一个新的线程被创建时都需要新分配一段大小为 1232KB 的内存空间,而我们总的虚拟内存限制是 200MB,所以如果我们需要创建更多的线程,那么一个可以改进的方法就是减少每个线程的固定堆栈大小,这可以通过 ulimit – s 来实现:

# ulimit -s 512

我们将堆栈大小设置为 512KB,这时再通过 pmap 查看一下我们的设置是否起作用:

图 9. 设置 ulimit 后堆栈大小


从上面的信息可以看出,我们已经成功的将线程的堆栈大小改为 512KB 了,这样在总内存使用限制不变的情况下,我们可以通过本小节介绍的方法来增加可以创建的线程数,从而达到改善程序的多线程性能。

总结

综上所述,linux 系统中的 ulimit 指令,对资源限制和系统性能优化提供了一条便捷的途径。从用户的 shell 启动脚本,应用程序启动脚本,以及直接在控制台,都可以通过该指令限制系统资源的使用,包括所创建的内核文件的大小、进程数据块的大小、Shell 进程创建文件的大小、内存锁住的大小、常驻内存集的大小、打开文件描述符的数量、分配堆栈的最大大小、CPU 时间、单个用户的最大线程数、Shell 进程所能使用的最大虚拟内存,等等方面。本文中的示例非常直观的说明了 ulimit 的使用及其产生的效果,显而易见,ulimit 对我们在 Linux 平台的应用和开发工作是非常实用的。

原文

https://www.ibm.com/developerworks/cn/linux/l-cn-ulimit/index.html