LINUXQQ

九月 1, 2009

dup函数

类归于: 乱7八糟 — 标签: — admin @ 9:39 上午

dup函数 :复制文件描述符
相关头文件:#include <unistd.h>
函数表达式:int dup(int filedes);
参数说明:dup的函数参数filedes表示需要复制的文件的文件描述符
返回值说明:如果成功复制一个文件返回新的文件描述符,失败则返回-1
函数功能详解:dup函数总是找到进程文件表中的第一个可用的文件描述符 ,将参数指定的文件描述符 复制到该描述符后 ,返回这个描述符。
函数使用说明:
        dup函数总是将复试的文件描述符复制到进程中第一个可用的文件描述符的位置 ,因此利用这一个特性可以知道进程中最小可用的文件描述符
        dup函数复制的文件必须是一个已经打开的有效文件 。dup函数的参数不能是随便的一个整数,从而避免引起系统混乱。

利用函数dup,我们可以复制一个描述符。传给该函数一个既有的描述符,它就会返回一个新的描述符,
这个新的描述符是传给它的描述符的拷贝。这意味着,这两个描述符共享同一个数据结构。例如,
如果我们对一个文件描述符执行lseek操作,得到的第一个文件的位置和第二个是一样的。

需要注意的是,我们可以在调用fork之前建立一个描述符,这与调用dup建立描述符的效果是一样的,
子进程也同样会收到一个复制出来的描述符。

dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的id。dup2函数成功返回时,目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,
两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。下面我们用一段代码加以说明: ?
int oldfd;
oldfd = open(“app_log”, (O_RDWR | O_CREATE), 0644 );
dup2( oldfd, 1 );
close( oldfd );
注:

dup()或者dup2()主要是将某个特定的文件描述字输出输出的重定向!
他们保证将复制的文件描述字到当前未打开的最小描述字!
标准输出对应着1,
close(1);
dup(fd);
close(fd);
先关掉了标准输出,那么这个描述字就空闲着,你一旦dup就重定向到他了,他们两共享同一个打开的文件表项,然后你将输出标准流到它现在指定的地方,也就是那个文件!
建议使用dup2(),上面的你那个改为
dup2(fd,STDOUT_FILENO);
这样好看多了吧?
本例中,我们打开了一个新文件,称为“app_log”,并收到一个文件描述符,该描述符叫做fd1。我们调用dup2函数,
参数为oldfd和1,这会导致用我们新打开的文件描述符替换掉由1代表的文件描述符(即stdout,因为标准输出文件的id为1)。
任何写到stdout的东西,现在都将改为写入名为“app_log”的文件中。

需要注意的是,dup2函数在复制了oldfd之后,会立即将其关闭,但不会关掉新近打开的文件描述符,因为文件描述符1现在也指向它。

下面我们介绍一个更加深入的示例代码:

1: #include <stdio.h>
 2: #include <stdlib.h>
3: #include <unistd.h>
 4:
 5: int main()

6: {
7: int pfds[2];
 8:
 9: if ( pipe(pfds) == 0 ) …{ //建立一个管道
 10:

 11: if ( fork() == 0 ) …{ //子进程
12:
13: close(1); //关闭stdout描述符
 14: dup2( pfds[1], 1 ); //把stdout重定向到管道(pfds[1])
 15: close( pfds[0] ); //关掉管道的输入端
 16: execlp( “ls”, “ls”, “-1″, NULL ); //把子进程的映像替换为命令ls –1的进程映像
 17:
18: }else …{ //父进程
 19:
 20: close(0); //关闭stdin描述符
 21: dup2( pfds[0], 0 ); //让stdin变成管道的输出端
 22: close( pfds[1] ); //关闭管道的stdout端(pfds[1])
 23: execlp( “wc”, “wc”, “-l”, NULL ); //把父进程的映像替换为命令wc -1的进程映像
 24:
 25: }
 26:
 27: }
 28:
 29: return 0;
 30: }

我们将ls –1命令的标准输出作为标准输入,连接到wc –l命令

首先在第9行代码中建立一个管道,然后将应用程序分成两个进程:一个子进程和一个父进程。

在子进程中首先关闭stdout描述符(第13行),然后提供了ls –1命令功能,
不过它不是写到stdout(第13行),而是写到我们建立的管道的输入端,这是通过dup函数来完成重定向的。

在第14行,使用dup2 函数把stdout重定向到管道(pfds[1])。之后,马上关掉管道的输入端。然后,使用execlp函数把子进程的映像替换为命令ls –1的进程映像,一旦该命令执行,它的任何输出都将发给管道的输入端。
现在来研究一下管道的接收端 。从代码中可以看出,管道的接收端是由父进程来担当的

首先关闭stdin描述符(第20行),因为我们不会从机器的键盘等标准设备文件来接收数据的输入,而是从其它程序的输出中接收数据。然后,再一次用到dup2函数(第21行),让stdin变成管道的输出端,这是通过让文件描述符0(即常规的stdin)等于pfds[0]来实现的。关闭管道的stdout端(pfds[1]),
因为在这里用不到它。最后,使用 execlp函数把父进程的映像替换为命令wc -1的进程映像,命令wc -1把管道的内容作为它的输入(第23行)。

在该程序中,需要格外关注的是,我们的子进程把它的输出重定向的管道的输入 ,然后,父进程将它的输入重定向到管道的输出。
这在实际的应用程序开发中是非常有用的一种技术。

1. 文件描述符在内核中数据结构
 在具体说dup/dup2之前, 我认为有必要先了解一下文件描述符在内核中的形态。
一个进程在此存在期间,会有一些文件被打开,从而会返回一些文件描述符,

从shell中运行一个进程, 默认会有3个文件描述符存在(0、1、2) ,

0与进程的标准输入相关联,
1与进程的标准输出相关联,

2与进程的标准错误输出相关联,

一个进程当前有哪些打开的文件描述符可以通过/proc/进程ID/fd目录查看 。 下图可以清楚的说明问题:

 进程表项

————————————————
   fd标志 文件指针
 _____________________
fd 0:|________|____________|————> 文件表
fd 1:|________|____________|

fd 2:|________|____________|

fd 3:|________|____________|

| ……. |

 |_____________________|

 图1

文件表中包含:文件状态标志、当前文件偏移量、v节点指针,这些不是本文讨论的
重点,我们只需要知道:

每个打开的文件描述符(fd标志)在进程表中都有自己的文件表项,由文件指针指向

2. dup/dup2函数
APUE和man文档都用一句话简明的说出了这两个函数的作用:复制一个现存的文件描述符。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

从图1来分析这个过程,当调用dup函数时,内核在进程中创建一个新的文件描述符,此
描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
  进程表项

————————————————

  fd标志 文件指针

 _____________________

fd 0:|________|____________| ______

fd 1:|________|____________|—————-> | |

fd 2:|________|____________| |文件表|

fd 3:|________|____________|—————-> |______|

 | ……. |

 |_____________________|

 图2:调用dup后的示意图
如图2 所示,假如oldfd的值为1, 当前文件描述符的最小值为3, 那么新描述符3指向
描述符1所拥有的文件表项。

dup2和dup的区别就是可以用newfd参数指定新描述符的数值,

如果newfd已经打开,则先将其关闭。

如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新
文件描述符同样与参数oldfd共享同一文件表项。
APUE用另外一个种方法说明了这个问题:
实际上,调用dup(oldfd);
等效与
? fcntl(oldfd, F_DUPFD, 0)
而调用dup2(oldfd, newfd);
等效与
 close(oldfd);
 fcntl(oldfd, F_DUPFD, newfd);

八月 26, 2009

信号屏蔽字

类归于: 乱7八糟 — 标签: — admin @ 4:06 下午

 

四、信号屏蔽字:

有时候我们希望进程正确的执行,而不想进程受到信号的影响,比如我们希望上面那个程序在1秒钟之后不结束。这个时候我们就要进行信号的操作了。

信号操作最常用的方法是信号屏蔽。信号屏蔽要用到下面的几个函数。

sigemptysetsigfillsetsigaddsetsigdelsetsigismembersigprocmask。下面对他们分别进行讲解。

 

8

名称:

sigemptyset/sigfillset/sigaddset/sigdelset/sigismember

功能

处理信号集

头文件

#include <signal.h>

函数原形:

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set,int signum);

int sigdelset(sigset_t *set,int signum);

int sigismember(const sigset_t *set,int signum);

参数:

set 信号集

signum 信号

返回值:

若成功返回0,若出错返回-1

若真返回1,若假返回0,若出错返回-1 sigismember

      

 

 

 

 

 

 

 

 

 我们需要有一个能表示多个信号—信号集的数据类型。我们将在诸如sigprocmask之类的函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号。上面的5个函数可以对信号集进行处理。

函数sigemptyset 初始化由set指向的信号集,清除其中所有信号。函数sigfillset初始化由set指向的信号集,使其包含所有信号。所以信号在使用信号集前,要对信号集调用sigemptysetsigfillset一次。

函数sigaddset 将一个信号添加到现有集中,sigdelset则从信号集中删除一个信号。对所有以信号集作为参数的函数,我们总是以信号集地址作为其传送的参数。

       sigismember查询信号是否在信号集合之中。

下面的例子:

/*10_7.c*/

#include <stdio.h>

#include <signal.h>

 

main()

{

sigset_t *set;

set=(sigset_t*)malloc(sizeof(set));

 

sigemptyset(set);/*初始化信号集*/

sigaddset(set,SIGUSR1);/*添加信号SIGUSR1到信号集中*/

sigaddset(set,SIGINT);/*添加信号SIGUSR2到信号集中*/

 

if((sigismember(set,SIGUSR1))==1)/*测试信号SIGUSR1是否在信号集中*/

    printf(“SIGUSR1\n”);

if((sigismember(set,SIGUSR2))==1)

    printf(“SIGUSR2\n”);

if((sigismember(set,SIGINT))==1)

    printf(“SIGINT\n”);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

下面是执行结果:

# ./10_7

SIGUSR1

SIGINT

程序先初始化信号集,清除其中所有信号,然后把SIGUSR1SIGINT添加到信号集中,然后测试SIGUSR1SIGUSR2SIGINT信号是否在信号集中。因为SIGUSR2不在信号集中,所以程序并不打印SIGUSR2

 

9

名称:

sigprocmask

功能

检测或更改信号屏蔽字

头文件

#include <signal.h>

函数原形

int sigprocmask(int how,const sigsett_t *set,sigset_t *oldset);

参数:

how  操作方式

set   信号集

oldest

返回值:

若成功返回0,若出错返回-1

 

 

 

 

 

 

 

 

 

 

 

 

每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。

sigprocmask是最为关键的一个函数。在使用之前要先设置好信号集合set。这个函数的作用是将指定的信号集合set加入到进程的信号阻塞集合之中去,如果提供了oldset那么当前的进程信号阻塞集合将会保存在oldset里面。参数how决定函数的操作方式。

SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之中。

SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合。

SIG_SETMASK:将当前的信号集合设置为信号阻塞集合。

 

我们把10_7.c稍微修改一下,看看sigprocmask函数的功能。

/*10_8.c*/

#include <stdio.h>

#include <signal.h>

 

main()

{

sigset_t *set;

set=(sigset_t*)malloc(sizeof(set));

 

sigemptyset(set);/*定义信号集set*/

sigaddset(set,SIGINT);/*把信号SIGINT添加到信号集中*/

sigprocmask(SIG_SETMASK,set,NULL);/*set设置为信号阻塞集合*/

wile(1);/*死循环*/

}

程序先定义信号集set,然后把信号SIGINT添加到set信号集中,最后把set设置为信号阻塞集合。当我们运行程序时,进程进入死循环。我们按“ctrl+c”系统并没有中断程序,因为我们已经把SIGINT信号屏蔽掉了。我们可以按”ctrl+z”来结束程序。

sigeprocmask函数通常和sigemptyset/sigfillset/sigaddset/sigdelset/sigismember函数配合使用,主要有两种用途:

1.我们不希望某些不太重要的信号来影响我们的进程,我们就可以把这些信号添加到信号屏蔽集中。使它们不打扰进程的执行。

2.如果系统现在很忙,没有时间及时相应信号,进程可以先把信号阻塞掉,等系统有空闲时间在去相应,这也保证了信号的可靠性。下面的函数可以做到这一点。

 

10

名称:

sigpending

功能:

返回信号集

头文件:

#include <signal.h>

函数原形:

int sigpending(sigset_t *set);

参数:

 

返回值:

若成功返回0,若出错返回-1

 

 

 

 

 

 

 

 

 

我们要注意的是,阻塞信号并不是丢弃信号,它们被保存在一个进程的信号阻塞队列里,sigpending可以获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回这些信号。

 

下面是一个例子:

/*10_9.c*/

#include <stdio.h>

#include <signal.h>

#include <stdlib.h>

 

int main(void)

{

sigset_t newmask,oldmask,pendmask;

 

sigemptyset(&newmask);  /*初始化信号集*/

sigaddset(&newmask,SIGINT);  /*添加信号SIGINT到信号集*/

if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)  /*信号集newmask设置为阻塞信号集*/

    perror(“error”);

sleep(5);

if(sigpending(&pendmask)<0)  /*把当前已递送到进程,却被阻塞的信号保存到进程的信号阻塞队列里*/

    perror(“error”);

if(sigismember(&pendmask,SIGINT))  /*检测进程阻塞队列里是否有SIGINT信号*/

    printf(“\nSIGINT pending\n”);

 

exit(0);

}

 

程序开始运行,如果我们在5秒钟内按”ctrl+c”,SIGINT信号会被传递到进程,但是由于进程设置了信号阻塞集,信号SIGINT在这个集合中,所有这个信号被阻塞。并由sigpend保存到进程的信号阻塞队列pendmask.然后我们用sigismember检测SIGINT是否在信号阻塞队列pendmask里。

 

八月 14, 2009

dlink-3026 基本配置手册

类归于: 乱7八糟 — 标签: — admin @ 2:19 下午

基本配置方法:

网络交换机的的配置说明书Show命令可以查看交换机的各项的配置(交换机无法查看整体的当前和保存的配置信息)
Create
命令可以创建交换的信息
Config
命令可d以对这些参数进行配置
Delete
命令可以删除所配制的参数
Reboot
命令对交换机进行重新启动

Save命令保存交换机的配置信息的修改
Reset
命令清空配置信息
reset system
不仅清空运行时配置,还将把曾经保存过的配置全部清空
reset config
仅清空运行时配置

Create account [admin|user]<username>  创建adminuser级别的用户,回车输入两次密码创建输入为空密码
Create account <username>
修改已创建的username用户,回车输入旧密码,在输入两次新密码Show account 可以看到你所创建的用户
Show switch   
可以查看交换机的信息
Show iproute   
查看路由表
Show vlan      
查看VLAN条目
[enable|disable] telnet 23            
此命令开启删除telnet功能
enable mac_notification               
默认是关闭MAC地址通告的
脚本:

create account admin admin    //创建管理员用户,用户名admin//

admin                         //password :admin//

admin                        //再确认password :admin//

create vlan vlan1242 tag 1242//创建VLAN1242,标记为1242//

 

config vlan default delete 1-24//删除默认1-24口默认VLAN//

config vlan vlan1242 add untagged 2-24//配置2-24口为VLAN1242,不打标记 //

config vlan vlan1242 add tagged 1 //配置1口为VLAN1242,打标记 //         

config ipif System ipaddress 192.168.1.2/255.255.255.0 vlan vlan1242 state enable

//VLAN1242上配置IP地址192.168.1.2//   

create iproute default 192.168.1.1//配置缺省网关 //

config traffic_segmentation 2-24 forward_list 1

//配置流量分割,端口隔离 //

create snmp host 117.131.2.227 v2c public

create snmp host 117.131.2.227 v2c private

//配置网管地址,主要用于告警的上发 //

save

   交换机支持TELNET的功能,配置完AP和交换机后,在超级终端ping AP的地址,来测试一下AP是否正常。再ping 网关确认移动的传输是否正常。

配置端口限速1M

config bandwidth_control 2-24 rx_rate 1024

config bandwidth_control 2-24 tx_rate 1024

七月 3, 2009

linux下C 文件操作

类归于: 乱7八糟 — 标签: — admin @ 10:39 上午

前言: 
    我们在这一节将要讨论linux下文件操作的各个函数. 
1.文件的创建和读写 
2.文件的各个属性 
3.目录文件的操作 
4.管道文件 

——————————————————————————–
1。文件的创建和读写 
    我假设你已经知道了标准级的文件操作的各个函数(fopen,fread,fwrite等等).当然如果你不清楚的话也不要着急.我们讨论的系统级的文件操作实际上是为标准级文件操作服务的. 
当我们需要打开一个文件进行读写操作的时候,我们可以使用系统调用函数open.使用完成以后我们调用另外一个close函数进行关闭操作. 
#include 
#include 
#include 
#include 

int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);

int close(int fd);

open函数有两个形式.其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面).flags可以去下面的一个值或者是几个值的组合. 
O_RDONLY:以只读的方式打开文件. 
O_WRONLY:以只写的方式打开文件. 
O_RDWR:以读写的方式打开文件. 
O_APPEND:以追加的方式打开文件. 
O_CREAT:创建一个文件. 
O_EXEC:如果使用了O_CREAT而且文件已经存在,就会发生一个错误. 
O_NOBLOCK:以非阻塞的方式打开一个文件. 
O_TRUNC:如果文件已经存在,则删除文件的内容. 
前面三个标志只能使用任意的一个.如果使用了O_CREATE标志,那么我们要使用open的第二种形式.还要指定mode标志,用来表示文件的访问权限.mode可以是以下情况的组合. 
—————————————————————–
S_IRUSR 用户可以读 S_IWUSR 用户可以写
S_IXUSR 用户可以执行 S_IRWXU 用户可以读写执行
—————————————————————–
S_IRGRP 组可以读 S_IWGRP 组可以写
S_IXGRP 组可以执行 S_IRWXG 组可以读写执行
—————————————————————–
S_IROTH         其他人可以读    S_IWOTH         其他人可以写
S_IXOTH         其他人可以执行  S_IRWXO         其他人可以读写执行
—————————————————————–
S_ISUID 设置用户执行ID  S_ISGID 设置组的执行ID
—————————————————————–
我们也可以用数字来代表各个位的标志.Linux总共用5个数字来表示文件的各种权限.
00000.第一位表示设置用户ID.第二位表示设置组ID,第三位表示用户自己的权限位,第四位表示组的权限,最后一位表示其他人的权限.
每个数字可以取1(执行权限),2(写权限),4(读权限),0(什么也没有)或者是这几个值的和.
比如我们要创建一个用户读写执行,组没有权限,其他人读执行的文件.设置用户ID位那么我们可以使用的模式是–1(设置用户ID)0(组没有设置)7(1+2+4)0(没有权限,使用缺省)5(1+4)即10705:
open(“temp”,O_CREAT,10705);
如果我们打开文件成功,open会返回一个文件描述符.我们以后对文件的所有操作就可以对这个文件描述符进行操作了.
当我们操作完成以后,我们要关闭文件了,只要调用close就可以了,其中fd是我们要关闭的文件描述符.
文件打开了以后,我们就要对文件进行读写了.我们可以调用函数read和write进行文件的读写.
#include 

ssize_t  read(int fd, void *buffer,size_t count);
ssize_t write(int fd, const void *buffer,size_t count);

fd是我们要进行读写操作的文件描述符,buffer是我们要写入文件内容或读出文件内容的内存地址.count是我们要读写的字节数.
对于普通的文件read从指定的文件(fd)中读取count字节到buffer缓冲区中(记住我们必须提供一个足够大的缓冲区),同时返回count.
如果read读到了文件的结尾或者被一个信号所中断,返回值会小于count.如果是由信号中断引起返回,而且没有返回数据,read会返回-1,且设置errno为EINTR.当程序读到了文件结尾的时候,read会返回0.
write从buffer中写count字节到文件fd中,成功时返回实际所写的字节数.
下面我们学习一个实例,这个实例用来拷贝文件.

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 1024

int main(int argc,char **argv)
{
 
 int from_fd,to_fd;
 int bytes_read,bytes_write;
 char buffer[BUFFER_SIZE];
 char *ptr;

 if(argc!=3)
  {
fprintf(stderr,”Usage:%s fromfile tofile\n\a”,argv[0]);
exit(1);
  }

/* 打开源文件 */

 if((from_fd=open(argv[1],O_RDONLY))==-1)
  {
fprintf(stderr,”Open %s Error:%s\n”,argv[1],strerror(errno));
exit(1);
 }

/* 创建目的文件 */

 if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
  {
        fprintf(stderr,”Open %s Error:%s\n”,argv[2],strerror(errno));
        exit(1);
 }
 
/* 以下代码是一个经典的拷贝文件的代码 */

 while(bytes_read=read(from_fd,buffer,BUFFER_SIZE))
 {
/* 一个致命的错误发生了 */
   if((bytes_read==-1)&&(errno!=EINTR)) break;
   else if(bytes_read>0)
       {
  ptr=buffer;
  while(bytes_write=write(to_fd,ptr,bytes_read))
   {
/* 一个致命错误发生了 */
     if((bytes_write==-1)&&(errno!=EINTR))break;
/* 写完了所有读的字节 */
     else if(bytes_write==bytes_read) break;
/* 只写了一部分,继续写 */
     else if(bytes_write>0)
           {
      ptr+=bytes_write;
   bytes_read-=bytes_write;
          }
          }
/* 写的时候发生的致命错误 */
         if(bytes_write==-1)break;

       }
  }
 close(from_fd);
 close(to_fd);
 exit(0);
}

2。文件的各个属性
    文件具有各种各样的属性,除了我们上面所知道的文件权限以外,文件还有创建时间,大小等等属性.
有时侯我们要判断文件是否可以进行某种操作(读,写等等).这个时候我们可以使用access函数.
#include 

int access(const char *pathname,int mode);

pathname:是文件名称,mode是我们要判断的属性.可以取以下值或者是他们的组合.
R_OK文件可以读,W_OK文件可以写,X_OK文件可以执行,F_OK文件存在.当我们测试成功时,函数返回0,否则如果有一个条件不符时,返回-1.
如果我们要获得文件的其他属性,我们可以使用函数stat或者fstat.
#include 
#include 

int stat(const char *file_name,struct stat *buf);
int fstat(int filedes,struct stat *buf);

struct stat {
dev_t st_dev; /* 设备   */
ino_t st_ino; /* 节点   */
mode_t st_mode; /* 模式   */
nlink_t st_nlink; /* 硬连接 */
uid_t st_uid; /* 用户ID */
gid_t st_gid; /* 组ID   */
dev_t st_rdev; /* 设备类型 */
off_t st_off; /* 文件字节数 */
unsigned long  st_blksize; /* 块大小 */
unsigned long st_blocks; /* 块数   */
time_t st_atime; /* 最后一次访问时间 */
time_t st_mtime; /* 最后一次修改时间 */
time_t st_ctime; /* 最后一次改变时间(指属性) */
};

stat用来判断没有打开的文件,而fstat用来判断打开的文件.我们使用最多的属性是st_mode.通过着属性我们可以判断给定的文件是一个普通文件还是一个目录,连接等等.可以使用下面几个宏来判断.
S_ISLNK(st_mode):是否是一个连接.S_ISREG是否是一个常规文件.S_ISDIR是否是一个目录S_ISCHR是否是一个字符设备.S_ISBLK是否是一个块设备S_ISFIFO是否 是一个FIFO文件.S_ISSOCK是否是一个SOCKET文件. 我们会在下面说明如何使用这几个宏的.
3。目录文件的操作
    在我们编写程序的时候,有时候会要得到我们当前的工作路径。C库函数提供了getcwd来解决这个问题。
#include 

char *getcwd(char *buffer,size_t size);

我们提供一个size大小的buffer,getcwd会把我们当前的路径考到buffer中.如果buffer太小,函数会返回-1和一个错误号.
Linux提供了大量的目录操作函数,我们学习几个比较简单和常用的函数.
#include 
#include 
#include 
#include 
#include 

int mkdir(const char *path,mode_t mode);
DIR *opendir(const char *path);
struct dirent *readdir(DIR *dir);
void rewinddir(DIR *dir);
off_t telldir(DIR *dir);
void  seekdir(DIR *dir,off_t off);
int closedir(DIR *dir);

struct dirent {
long  d_ino;
off_t  d_off;
unsigned short d_reclen;
char  d_name[NAME_MAX+1]; /* 文件名称 */

mkdir很容易就是我们创建一个目录,opendir打开一个目录为以后读做准备.readdir读一个打开的目录.rewinddir是用来重读目录的和我们学的rewind函数一样.closedir是关闭一个目录.telldir和seekdir类似与ftee和fseek函数.
下面我们开发一个小程序,这个程序有一个参数.如果这个参数是一个文件名,我们输出这个文件的大小和最后修改的时间,如果是一个目录我们输出这个目录下所有文件的大小和修改时间.

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static int get_file_size_time(const char *filename)
{
 struct stat statbuf;
 
 if(stat(filename,&statbuf)==-1)
  {
printf(“Get stat on %s Error:%s\n”,
filename,strerror(errno));
return(-1);
  }
  
 if(S_ISDIR(statbuf.st_mode))return(1);
 if(S_ISREG(statbuf.st_mode))
printf(“%s size:%ld bytes\tmodified at %s”,
filename,statbuf.st_size,ctime(&statbuf.st_mtime)); 

 return(0);
}

int main(int argc,char **argv)
{
 DIR *dirp;
 struct dirent *direntp; 
 int stats;

 if(argc!=2)
  {
printf(“Usage:%s filename\n\a”,argv[0]);
exit(1);
  }

 if(((stats=get_file_size_time(argv[1]))==0)||(stats==-1))exit(1);
 
 if((dirp=opendir(argv[1]))==NULL)
 {
printf(“Open Directory %s Error:%s\n”,
argv[1],strerror(errno));
exit(1);
 }

 while((direntp=readdir(dirp))!=NULL)
   if(get_file_size_time(direntp- closedir(dirp);
 exit(1);
}

4。管道文件
    Linux提供了许多的过滤和重定向程序,比如more cat
等等.还提供了< > | <<等等重定向操作符.在这些过滤和重 定向程序当中,都用到了管道这种特殊的文件.系统调用pipe可以创建一个管道.
#include

int pipe(int fildes[2]);

pipe调用可以创建一个管道(通信缓冲区).当调用成功时,我们可以访问文件描述符fildes[0],fildes[1].其中fildes[0]是用来读的文件描述符,而fildes[1]是用来写的文件描述符.
在实际使用中我们是通过创建一个子进程,然后一个进程写,一个进程读来使用的.
关于进程通信的详细情况请查看进程通信

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define  BUFFER 255

int main(int argc,char **argv)
{
  char buffer[BUFFER+1];
  int fd[2];

  if(argc!=2)
  {
fprintf(stderr,”Usage:%s string\n\a”,argv[0]);
exit(1);
  }

  if(pipe(fd)!=0)
  {
fprintf(stderr,”Pipe Error:%s\n\a”,strerror(errno));
exit(1);
  }
  if(fork()==0)
   {
close(fd[0]);
printf(“Child[%d] Write to pipe\n\a”,getpid());
snprintf(buffer,BUFFER,”%s”,argv[1]);
write(fd[1],buffer,strlen(buffer));
printf(“Child[%d] Quit\n\a”,getpid());
exit(0);
   }
 else
  {
close(fd[1]);
printf(“Parent[%d] Read from pipe\n\a”,getpid());
memset(buffer,’\0′,BUFFER+1);
read(fd[0],buffer,BUFFER);
printf(“Parent[%d] Read:%s\n”,getpid(),buffer);
   exit(1);
  }
}

为了实现重定向操作,我们需要调用另外一个函数dup2.
#include 

int dup2(int oldfd,int newfd);

dup2将用oldfd文件描述符来代替newfd文件描述符,同时关闭newfd文件描述符.也就是说,
所有向newfd操作都转到oldfd上面.下面我们学习一个例子,这个例子将标准输出重定向到一个文件.

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define  BUFFER_SIZE 1024

int main(int argc,char **argv)
{
 int fd;
 char buffer[BUFFER_SIZE];

 if(argc!=2)
  {
fprintf(stderr,”Usage:%s outfilename\n\a”,argv[0]);
exit(1);
  }

 if((fd=open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR))==-1)
  {
  fprintf(stderr,”Open %s Error:%s\n\a”,argv[1],strerror(errno));
exit(1);
  }

 if(dup2(fd,STDOUT_FILENO)==-1)
  {
fprintf(stderr,”Redirect Standard Out Error:%s\n\a”,strerror(errno));
exit(1);
  }

 fprintf(stderr,”Now,please input string”);
 fprintf(stderr,”(To quit use CTRL+D)\n”);  
 while(1)
  {
    fgets(buffer,BUFFER_SIZE,stdin); 
    if(feof(stdin))break;
    write(STDOUT_FILENO,buffer,strlen(buffer));
 }
 exit(0);
}

好了,文件一章我们就暂时先讨论到这里,学习好了文件的操作我们其实已经可以写出一些比较有用的程序了.我们可以编写一个实现例如dir,mkdir,cp,mv等等常用的文件操作命令了.
想不想自己写几个试一试呢?

六月 30, 2009

squid 更新周期 图片和FLASH的

类归于: 乱7八糟 — 标签: — admin @ 12:33 下午

讲这些语句加到squid配置里面 我用的是 squid 2.5

refresh_pattern .               0       20%     4320
refresh_pattern -i \.(htm|html|txt|js)$         0       66%     1440    reload-into-ims
refresh_pattern -i \.(bmp|gif|jpg|png|swf)$     0       66%     4320   reload-into-ims

六月 29, 2009

squid访问量大造成访问速度慢的终极解决办法

类归于: 乱7八糟 — 标签: — admin @ 10:49 上午

  最近做squid 发现放量巨大 造成squid日志出现WARNING! Your cache is running out of filedescriptors

 我是squid 2.5 

解决办法

# ulimit -SHn 2048
# vi /usr/include/bits/typesizes.h

#define __FD_SETSIZE 1024
改成
#define __FD_SETSIZE 2048

然后重新编译你的squid
然后把
ulimit -SHn 2048这行放到你的squid的启动脚本的最前面
你可以从redhat的源代码包自己编译:
rpm -i squid-2.4.STABLE7-4.src.rpm
ulimit -SHn 4096 /usr/local/squid/sbin/squid
vi /usr/include/bits/typesizes.h (to change __FD_SETSIZE)

编译完后记得把typesizes.h里的2048改回来

 

网上的搜索到的

 

六月 25, 2009

APACHE优化 转载

类归于: 乱7八糟 — 标签: — admin @ 1:26 上午

手头有一个网站在线人数增多,访问时很慢。初步认为是服务器资源不足了,但经反复测试,一旦连接上,不断点击同一个页面上不同的链接,都能迅速打开,这种 现象就是说明apache最大连接数已经满了,新的访客只能排队等待有空闲的链接,而如果一旦连接上,在keeyalive 的存活时间内(KeepAliveTimeout,默认5秒)都不用重新打开连接,因此解决的方法就是加大apache的最大连接数。

1.在哪里设置?

服务器的为FreeBSD 6.2 ,apache 2.24,使用默认配置(FreeBSD 默认不加载自定义MPM配置),默认最大连接数是250

在/usr/local/etc/apache22/httpd.conf中加载MPM配置(去掉前面的注释):
# Server-pool management (MPM specific)
Include etc/apache22/extra/httpd-mpm.conf

可见的MPM配置在/usr/local/etc/apache22/extra/httpd-mpm.conf,但里面根据httpd的工作模式分了很多块,哪一部才是当前httpd的工作模式呢?可通过执行 apachectl -l 来查看:
Compiled in modules:
core.c
prefork.c
http_core.c
mod_so.c

看到prefork 字眼,因此可见当前httpd应该是工作在prefork模式,prefork模式的默认配置是:
<IfModule mpm_prefork_module>
StartServers                      5
MinSpareServers                   5
MaxSpareServers                  10
MaxClients                      150
MaxRequestsPerChild               0
</IfModule>

2.要加到多少?

连接数理论上当然是支持越大越好,但要在服务器的能力范围内,这跟服务器的CPU、内存、带宽等都有关系。

查看当前的连接数可以用:
ps aux | grep httpd | wc -l

或:
pgrep httpd|wc -l

计算httpd占用内存的平均数:
ps aux|grep -v grep|awk ‘/httpd/{sum+=$6;n++};END{print sum/n}’

由于基本都是静态页面,CPU消耗很低,每进程占用内存也不算多,大约200K。

服务器内存有2G,除去常规启动的服务大约需要500M(保守估计),还剩1.5G可用,那么理论上可以支持1.5*1024*1024*1024/200000 = 8053.06368

约8K个进程,支持2W人同时访问应该是没有问题的(能保证其中8K的人访问很快,其他的可能需要等待1、2秒才能连上,而一旦连上就会很流畅)

控制最大连接数的MaxClients ,因此可以尝试配置为:
<IfModule mpm_prefork_module>
StartServers                      5
MinSpareServers                   5
MaxSpareServers                  10
ServerLimit                    5500
MaxClients                     5000
MaxRequestsPerChild               100
</IfModule>

注意,MaxClients默认最大为250,若要超过这个值就要显式设置ServerLimit,且ServerLimit要放在MaxClients之前,值要不小于MaxClients,不然重启httpd时会有提示。

重启httpd后,通过反复执行pgrep httpd|wc -l 来观察连接数,可以看到连接数在达到MaxClients的设值后不再增加,但此时访问网站也很流畅,那就不用贪心再设置更高的值了,不然以后如果网站访 问突增不小心就会耗光服务器内存,可根据以后访问压力趋势及内存的占用变化再逐渐调整,直到找到一个最优的设置值。

(MaxRequestsPerChild不能设置为0,可能会因内存泄露导致服务器崩溃)

更佳最大值计算的公式:

apache_max_process_with_good_perfermance < (total_hardware_memory / apache_memory_per_process ) * 2
apache_max_process = apache_max_process_with_good_perfermance * 1.5

参考:

apache的参数设置

Apache 2.0性能优化—MPM的选择与配置

如何避免apache的httpd进程占用比较多的内存

对apache中并发控制参数prefork理解和调优

附:

实时检测HTTPD连接数:
watch -n 1 -d “pgrep httpd|wc -l”

如果还出现慢的情况,那就只有在apache服务器上再加个squid,这样速度应该会有明显提速

六月 15, 2009

fprintf函数

类归于: 乱7八糟 — 标签: — admin @ 10:27 上午

查了一下man手册,fprintf函数的原型如下:
     int fprintf(FILE *stream, const char *format, …);
      FILE *fp=NULL;
     fp=fopen(HTTP_CONF_FILE,”w”);
     if (fp==NULL){
         err(“fopen() error,file:%s!!!\n”,HTTP_CONF_FILE);
         return FAILURE;
     }
     fprintf(fp,”ServerRoot \”%s\”\r\n”,HTTP_SERVER_ROOT);
     fprintf(fp,”Listen %u\r\n”,http_setup->net.http_port);
     fprintf(fp,”\r\n”);
首先是生命一个FILE类型的流文件指针,调用fopen打开文件并执行写的操作, 然后调用fprintf将配置文件按照Appweb规定的格式写入。

标准库函数perror用法(打印出错误原因信息字符串)

类归于: 乱7八糟 — 标签: — admin @ 10:25 上午

表头文件 
 #include<stdio.h>
 
定义函数 
 void perror(const char *s);
 
函数说明
 perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 错误
 (stderr) 。参数 s 所指的字符串会先打印出,后面再加上错误原因
 字符串。此错误原因依照全局变量 errno 的值来决定要输出的字符串。
范例:
#include <stdio.h>
int main(void)
{
 FILE *fp ;
 fp = fopen( ”/root/noexitfile”, ”r+” );
 if ( NULL == fp )
 {
  perror(“/root/noexitfile”);
 }
 
 return 0;
}
运行结果:
[root@localhost io]# gcc perror.c 
[root@localhost io]# ./a.out 
/root/noexitfile: No such file or directory

六月 14, 2009

Linux下的管道编程技术-dup函数和dup2函数

类归于: 乱7八糟 — 标签: — admin @ 5:25 下午

       dup和dup2也是两个非常有用的调用,它们的作用都是用来复制一个文件的描述符。它们经常用来重定向进程的stdin、stdout和stderr。这两个函数的原型如下所示:
#include
int dup( int oldfd );
int dup2( int oldfd, int targetfd )
利用函数dup,我们可以复制一个描述符。传给该函数一个既有的描述符,它就会返回一个新的描述符,这个新的描述符是传给它的描述符的拷贝。这意味着,这两个描述符共享同一个
数据结构
。例如,如果我们对一个文件描述符执行lseek操作,得到的第一个文件的位置和第二个是一样的。下面是用来说明dup函数使用方法的代码片段:
int fd1, fd2;

fd2 = dup( fd1 );
需要注意的是,我们可以在调用fork之前建立一个描述符,这与调用dup建立描述符的效果是一样的,子进程也同样会收到一个复制出来的描述符。

dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的id。dup2函数成功返回时,目标描述符(dup2函数的第
二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。下面
我们用一段代码加以说明:
    int oldfd;
oldfd = open(“app_log”, (O_RDWR | O_CREATE), 0644 );
dup2( oldfd, 1 );
close( oldfd );

本例中,我们打开了一个新文件,称为“app_log”,并收到一个文件描述符,该描述符叫做fd1。我们调用dup2函数,参数为oldfd和1,这会
导致用我们新打开的文件描述符替换掉由1代表的文件描述符(即stdout,因为标准输出文件的id为1)。任何写到stdout的东西,现在都将改为写
入名为“app_log”的文件中。需要注意的是,dup2函数在复制了oldfd之后,会立即将其关闭,但不会关掉新近打开的文件描述符,因为文件描述
符1现在也指向它。
下面我们介绍一个更加深入的示例代码。回忆一下本文前面讲的命令行管道,在那里,我们将ls –1命令的标准输出作为标准输入连接到wc –l命令。接下来,我们就用一个C程序来加以说明这个过程的实现。代码如下面的示例代码3所示。

在示例代码3中,首先在第9行代码中建立一个管道,然后将应用程序分成两个进程:一个子进程(第13–16行)和一个父进程(第20–23行)。接下来,
在子进程中首先关闭stdout描述符(第13行),然后提供了ls
–1命令功能,不过它不是写到stdout(第13行),而是写到我们建立的管道的输入端,这是通过dup函数来完成重定向的。在第14行,使用dup2
函数把stdout重定向到管道(pfds[1])。之后,马上关掉管道的输入端。然后,使用execlp函数把子进程的映像替换为命令ls
–1的进程映像,一旦该命令执行,它的任何输出都将发给管道的输入端。

现在来研究一下管道的接收端。从代码中可以看出,管道的接收端是由父进程来担当的。首先关闭stdin描述符(第20行),因为我们不会从机器的键盘等标
准设备文件来接收数据的输入,而是从其它程序的输出中接收数据。然后,再一次用到dup2函数(第21行),让stdin变成管道的输出端,这是通过让文
件描述符0(即常规的stdin)等于pfds[0]来实现的。关闭管道的stdout端(pfds[1]),因为在这里用不到它。最后,使用
execlp函数把父进程的映像替换为命令wc -1的进程映像,命令wc -1把管道的内容作为它的输入(第23行)。
示例代码3:利用C实现命令的流水线操作的代码
    1:    #include
    2:    #include
    3:    #include
    4:
    5:    int main()
    6:    …{
    7:       int pfds[2];
    8:
    9:       if ( pipe(pfds) == 0 ) …{
    10:
    11:           if ( fork() == 0 ) …{
    12:
    13:          close(1);
    14:          dup2( pfds[1], 1 );
    15:          close( pfds[0] );
    16:          execlp( “ls”, “ls”, “-1″, NULL );
    17:
    18:           } else …{
    19:
    20:          close(0);
    21:          dup2( pfds[0], 0 );
    22:          close( pfds[1] );
    23:          execlp( “wc”, “wc”, “-l”, NULL );
    24:
    25:           }
    26:
    27:       }
    28:
    29:       return 0;
    30:    }
    在该程序中,需要格外关注的是,我们的子进程把它的输出重定向的管道的输入,然后,父进程将它的输入重定向到管道的输出。这在实际的应用程序开发中是非常有用的一种技术。
1. 文件描述符在内核中数据结构
在具体说dup/dup2之前, 我认为有必要先了解一下文件描述符在内核中的形态。
一个进程在此存在期间,会有一些文件被打开,从而会返回一些文件描述符,从shell
中运行一个进程,默认会有3个文件描述符存在(0、1、2), 0与进程的标准输入相关联,
1与进程的标准输出相关联,2与进程的标准错误输出相关联,一个进程当前有哪些打开
的文件描述符可以通过/proc/进程ID/fd目录查看。 下图可以清楚的说明问题:
  进程表项
————————————————
   fd标志 文件指针
   _____________________
fd 0:|________|____________|————> 文件表
fd 1:|________|____________|
fd 2:|________|____________|
fd 3:|________|____________|
    |     …….       |
    |_____________________|
            图1
       
文件表中包含:文件状态标志、当前文件偏移量、v节点指针,这些不是本文讨论的
重点,我们只需要知道每个打开的文件描述符(fd标志)在进程表中都有自己的文件表
项,由文件指针指向。
2. dup/dup2函数
APUE和man文档都用一句话简明的说出了这两个函数的作用:复制一个现存的文件描述符。
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
从图1来分析这个过程,当调用dup函数时,内核在进程中创建一个新的文件描述符,此
描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
  进程表项
————————————————
   fd标志 文件指针
   _____________________
fd 0:|________|____________|                ______
fd 1:|________|____________|—————-> |    |
fd 2:|________|____________|                |文件表|
fd 3:|________|____________|—————-> |______|
    |     …….       |
    |_____________________|
            图2:调用dup后的示意图
如图2 所示,假如oldfd的值为1, 当前文件描述符的最小值为3, 那么新描述符3指向
描述符1所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则
先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新
文件描述符同样与参数oldfd共享同一文件表项。
APUE用另外一个种方法说明了这个问题:
实际上,调用dup(oldfd);
等效与
       fcntl(oldfd, F_DUPFD, 0)
而调用dup2(oldfd, newfd);
等效与
       close(oldfd);
       fcntl(oldfd, F_DUPFD, newfd);
3. CGI中dup2
写过CGI程序的人都清楚,当浏览器使用post方法提交表单数据时,CGI读数据是从标准
输入stdin, 写数据是写到标准输出stdout(c语言利用printf函数)。按照我们正常的理
解,printf的输出应该在终端显示,原来CGI程序使用dup2函数将STDOUT_FINLENO(这个
宏在unitstd.h定义,为1)这个文件描述符重定向到了连接套接字。
dup2(connfd, STDOUT_FILENO); /*实际情况还涉及到了管道,不是本文的重点*/
如第一节所说, 一个进程默认的文件描述符1(STDOUT_FILENO)是和标准输出stdout相
关联的,对于内核而言,所有打开的文件都通过文件描述符引用,而内核并不知道流的
存在(比如stdin、stdout),所以printf函数输出到stdout的数据最后都写到了文件描述
符1里面。至于文件描述符0、1、2与标准输入、标准输出、标准错误输出相关联,这
只是shell以及很多应用程序的惯例,而与内核无关。
用下面的流图可以说明问题:(ps: 虽然不是流图关系,但是还是有助于理解)
printf -> stdout -> STDOUT_FILENO(1) -> 终端(tty)
printf最后的输出到了终端设备,文件描述符1指向当前的终端可以这么理解:
STDOUT_FILENO = open(“/dev/tty”, O_RDWR);
使用dup2之后STDOUT_FILENO不再指向终端设备, 而是指向connfd, 所以printf的
输出最后写到了connfd。是不是很优美?:)
4. 如何在CGI程序的fork子进程中还原STDOUT_FILENO
如果你能看到这里,感谢你的耐心, 我知道很多人可能感觉有点复杂, 其实
复杂的问题就是一个个小问题的集合。所以弄清楚每个小问题就OK了,第三节中
说道,STDOUT_FILENO被重定向到了connfd套接字, 有时候我们可能想在CGI程序
中调用后台脚本执行,而这些脚本中难免会有一些输入输出, 我们知道fork之后,
子进程继承了父进程的所有文件描述符,所以这些脚本的输入输出并不会如我们愿
输出到终端设备,而是和connfd想关联了,这个显然会扰乱网页的输出。那么如何
恢复STDOUT_FILENO和终端关联呢?
方法1:在dup2之前保存原有的文件描述符,然后恢复。
代码实现如下:
savefd = dup(STDOUT_FILENO); /*savefd此时指向终端*/
dup2(connfd, STDOUT_FILENO); /*STDOUT_FILENO(1) 被重新指向connfd*/
…..   /*处理一些事情*/
dup2(savefd, STDOUT_FILENO);   /*STDOUT_FILENO(1) 恢复指向savefd*/
很遗憾CGI程序无法使用这种方法, 因为dup2这些不是在CGI程序中完成的,而是在
web server中实现的,修改web server并不是个好主意。
方法2: 追本溯源,打开当前终端恢复STDOUT_FILENO。
分析第三节的流图, STDOUT_FILENO是如何和终端关联的? 我们重头做一遍不就行
了, 代码实现如下:
ttyfd = open(“/dev/tty”, O_RDWR);
dup2(ttyfd, STDOUT_FILENO);
close(ttyfd);
/dev/tty是程序运行所在的终端, 这个应该通过一种方法获得。实践证明这种方法
是可行的,但是我总感觉有些不妥,不知道为什么,可能一些潜在的问题还没出现。
目前我就想到这两种方法, 不知道你有什么好的想法? 有的话希望告诉我:)

« 较近文章早前文章 »

WordPress 所驱动