分类 未分类 下的文章

linux编程学习 04 进程间通信

进程间通信概述

  • 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间
  • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件(如进程终止时要通知父进程)
  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能知道他的状态和改变

进程间通信的变革

  • linux进程间通信(IPC)由以下几个部分发展而来

    • 早期unix进程间通信
    • 基于System V进程间通信
    • 基于socket进程间通信和POSIX进程间通信
  • unix进程间通信的方式包括:管道,FIFO,信号
  • System V进程间通信方式包括:System V消息队列,System V信号灯,System V共享内存
  • POSIX进程间通信包括:POSIX消息队列,POSIX信号灯,POSIX共享内存

现代的进程间通信的方式

  • 管道(pipe)和命名管道(FIFO)
  • 信号(signal)
  • 消息队列
  • 共享内存
  • 信号量
  • 套接字(socket)

管道通信

  • 管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际获得两个文件描述符:一个用于读取而另一个用于写入
  • 最常见的IPC通信机制,通过pipe系统调用
  • 管道是单工的,数据只能向一个方向流动,需要双向通信时,需要建立起两个管道
  • 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区头部读出数据
  • 本质上是在内核中的一个缓存

管道分类

  • 匿名管道

    • 在关系进程中进行(父进程和子进程,兄弟进程之间)
    • 由pipe系统调用,管道由父进程建立
    • 管道位于内核空间,其实是一块缓存
  • 命名管道

    • 两个没有任何关系的进程之间通信可通过命名管道进行数据传输,本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在
    • 通过系统调用mkfifo创建

匿名管道创建

int pipe(int fp[2]);

  • 返回:成功返回0,出错返回-1
  • 头文件:
#include <unistd.h>
  • 两个文件描述符数组

    • fd[0]:为pipe的读端
    • fd[1]:为pipe的写端
    • fd[0]用于读取管道,fd[1]用于写入管道

管道读写

管道主要用于不同进程间通信,实际上,通常先创建一个管道,再通过fork函数创建一个子进程

示例

基本使用
int main(void)
{
        int fp[2];
        if(pipe(fp) == -1)
        {
                perror("create pipe error");
                return 1;
        }
        int pid = fork();
        int i = 0;
        if(pid == -1)
        {
                perror("fork error");
                return 1;
        }
        else if(pid > 0)
        {
                // 父进程写入,先关闭管道读端
                close(fp[0]);
                i = 8;
                if(sizeof(int) != write(fp[1],(void*)&i,sizeof(int)))
                {
                        perror("first write error");
                        return 1;
                }
                i = 88;
                if(sizeof(int) != write(fp[1],(void*)&i,sizeof(int)))
                {
                        perror("second write error");
                        return 1;
                }
                wait(NULL);
                close(fp[1]);
        }
        else if(pid == 0)
        {
                // 子进程读,先关闭管道写端
                close(fp[1]);
                int j = 0;
                if(-1 == read(fp[0],(void*)&j,sizeof(int)))
                {
                        perror("first read error");
                        return 1;
                }
                printf("first read;i = %d\n",j);
                if(-1 == read(fp[0],(void*)&j,sizeof(int)))
                {
                        perror("second read error");
                        return 1;
                }
                printf("second read;i = %d\n",j);
                close(fp[0]);
        }

        return 0;
}
达到 cat /etc/passwd | grep root 的效果
#include <stdio.h>
#include <unistd.h>
char* command1[] = {"cat","/etc/passwd",NULL};
char* command2[] = {"grep","root",NULL};
int main(void)
{
        int fp[2];
        // 备份标准输出
        int stdout_copy = dup(STDOUT_FILENO);
        if(-1 == pipe(fp))
        {
                perror("pipe error");
                return 1;
        }
        int pid = fork();
        if(pid == -1)
        {
                perror("first fork error");
                return 1;
        }
else if(pid == 0)// 读取内容进程
        {
                // 关闭读端
                close(fp[0]);
                // cat命令默认从标准输出读取数据,使用dup2命令把标准输出转换成管道的输入端
                dup2(fp[1],STDOUT_FILENO);
                close(fp[1]);
                if(execvp(command1[0],command1) == -1)
                {
                        // 显示出错信息前先把标准输出还原
                        dup2(stdout_copy,STDOUT_FILENO);
                        printf("fail to execute %s",command1[0]);
                        perror("\n");
                        return 1;
                }
        }
        else if(pid > 0)
        {
                pid = fork();
                if(pid == -1)
                {
                        perror("second fork error");
                        return 1;
                }
                else if(pid == 0)// 过滤内容进程
                {
                        // 关闭写端
                        close(fp[1]);
                        // 和上面进程一样,把标准输入转换成管道出口
                        dup2(fp[0],STDIN_FILENO);
                        close(fp[0]);
                        if(execvp(command2[0],command2) == -1)
                        {
                                dup2(stdout_copy,STDOUT_FILENO);
                                printf("fail to execute %s",command2[0]);
                                perror("\n");
                                return 1;
                        }
                }
                else// 主进程
                {
                        // 回收进程资源
                        printf("\n");
                        wait(NULL);
                        // 关闭
                        close(fp[0]);
                        close(fp[1]);
                }
        }
        return 0;
}

管道的读写特性

  • 通过打开两个管道来创建一个双向的管道
  • 管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞
  • 当一个进程往管道中不断写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但若管道放满数据则会报错
  • 不完整管道

    • 当读一个写端已经被关闭的管道时,在所有数据被读取后,read返回0,以表示到达了文件末尾
    • 如果写一个读端已被关闭的管道,则产生信号SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则write返回-1,同时errno设置为EPIPE
  • 示例
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void func1(void);
void func2(void);
int main(void)
{
        func2();
        return 0;
}
/* 读取写端被关闭的管道  */
void func1(void)
{
        int fp[2];
        if(-1 == pipe(fp))
        {
                return;
        }
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                return;
        }
        else if(pid == 0)
        {
                close(fp[0]);
                write(fp[1],"I am child precess\n",19);
                close(fp[1]);
        }
        else
        {
                sleep(3);
                close(fp[1]);
                char c = '\0';
                while(1)
                {
                        // 写端被关闭,读完管道中的数据后返回0,不会阻塞
                        if(0 == read(fp[0],&c,1))
                        {
                                break;
                        }
                        printf("%c",c);
                }
                close(fp[0]);
        }
}
void sigpipe_handle(int sig)
{
        printf("I am sigpipe_handle\n");
        return;
}
/* 往读端被关闭的管道写入数据 */
void func2(void)
{
        int fp[2];
        pipe(fp);
        close(fp[1]);
        signal(SIGPIPE,sigpipe_handle);
        if(-1 == write(fp[0],"8",1))
        {
                perror("write pipe error");
                return;
        }
        close(fp[0]);
}

标准库中的管道操作

FILE* popen(const char* cmdstring,const char* type);

  • 返回值:成功返回文件指针,出错返回NULL
    int pclose(FILE *fp);
  • 返回值:cmdstring的终止状态,出错返回-1
  • 头文件
#include <stdio.h>
  • 使用popen创建的管道必须使用pclose关闭。其实popen/pclose和标准文件输入/输出流中的fopen/fclose十分相似
  • 封装管道的常用操作
  • 示例
int main(void)
{
        // 读
        FILE *f = popen("cat /etc/passwd","r");
        char str[1024] = {'\0'};
        while(NULL != fgets(str,1024,f))
        {
                // printf("%s",str);
        }
        pclose(f);
        // 写
        f = popen("wc -l","w");
        fprintf(f,"/etc/passwd\n/etc/shadow");
        pclose(f);
        return 0;
}

命名管道FIFO创建

int mkfifo(const char* pathname,mmode_t mode);

  • 返回:成功返回0,出错返回-1
  • 头文件
#include <sys/types.h>
#include <sys/stat.h>
  • 只要对FIFO有适当访问权限,FIFO可用在任何两个没有任何关系的进程之间通信。
  • 本质是内核的一块缓存,另外在文件系统中以一个特殊的设备文件(管道文件)存在
  • 在文件系统中只有一个索引块存放文件的路径,没有数据块,所有数据存放在内核中。
  • 命名管道必须读和写同时打开,否则单独读或者单独写会引发阻塞
  • 命名mkfifo创建命名管道(命令内部调用mkfifo函数)
  • 对FIFO的操作与操作普通文件一样
  • 一旦已经用mkfifo创建了一个FIFO,就可以用open打开它,一般文件I/O函数(open,close,write,read,unlink等)都可用于FIFO
  • FIFO相关出错信息

    • EACCES(无存取权限)
    • EEXIST(指定文件不存在)
    • ENAMETOOLONG(路径名太长)
    • ENOENT(包含的目录不存在)
    • ENOSPC(文件系统剩余空间不足)
    • ENOTDIR(文件路径无效)
    • EROFS(指定文件存在于只读文件系统中)
  • 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorret parameter\n");
                return 1;
        }
        // 创建管道文件
        char *path = "/tmp/fifo_test";
        mkfifo("/tmp/fifo_test",0777);
        int f = open(path,O_RDWR);
        char str[8] = {'\0'}; 
        // 可以启动两个,一个读取一个写入
        if(strcmp("w",argv[1]) == 0)
        {
                scanf("%s",str);
                write(f,str,8);
        }
        else if(strcmp("r",argv[1]) == 0)
        {
                read(f,str,8);
                printf("str = %s\n",str);
        }
        close(f);
        return 0;
}

System V IPC

概述

  • unix系统存在信号,管道和命名通道等基本进程间通信机制
  • System V引入了三种高级进程间通信机制

    • 消息队列,共享内存和信号量
  • IPC对象(消息队列,共享内存和信号量)存在于内核中而不是文件系统中,由用户控制释放(用户管理IPC对象的生命周期),不像管道的释放由内核控制
  • IPC对象通过其标识符来引用和访问,所有IPC对象在内核空间中有唯一性标识,在用户空间中的唯一性标识称为key
  • linux IPC继承自System V IPC

System V IPC对象访问

  • IPC对象是全局对象

    • 可用ipcs,ipcrm等命令查看或删除
  • 每个IPC对象都由get函数创建

    • msgget,shmget,semget
    • 调用get函数时必须指定关键字key

IPC对象的权限和所有者结构体

struct ipc_perm
{
        uid_t uid;// owner's effective user id
        gid_t gid;// owner's effective group id
        uid_t cuid;// creator's effective user id
        gid_t cgid;// creator's effective group id
        mode_t mode;// access mode
        ......
};

消息队列

  • 消息队列是内核中的一个链表
  • 用户进程将数据传输到内核后,内核重现添加一些如用户id,组id,读写进程的id和优先级等相关信息后并打包成一个数据包称为消息
  • 允许一个或多个进程往消息队列中写消息读消息,但一个消息只能被一个进程读取,读取完毕后就自动删除
  • 消息队列具有一定的FIFO的特性,消息可以按照顺序发送到队列中,也可以以几种不同的方式从队列中读取。每一个消息队列在内核中用一个唯一的IPC标识id表示。
  • 消息队列的实现包括创建和打开队列,发送消息,读取消息和控制消息队列四种操作
消息队列属性
struct msqid_ds{
        struct ipc_perm msg_perm;
        msgqnum_t msg_qnum;// of message on queue
        msglen_t msg_qbytes;// max of bytes on queue 最大消息字节数
        pid_t msg_lspid;// pid of last msgsnd() 最后一次发送消息的进程的pid
        pid_t msg_lrpid;// pid of last msgrcv() 最后一次接受消息的进程的pid
        time_t msg_stime;// last msgsnd time 最后发送消息的时间
        time_t msg_ctime;// last change time 最后一次改变的时间
......
};
打开或创建消息队列

int msgget(key_t key,int flag);

  • 返回:成功返回内核中消息队列的表示id,出错返回-1
  • 头文件
#include <sys/msg.h>
  • 参数

    • key:用户指定的消息队列的键值
    • flag:IPC_CREAT,IPC_EXCL等权限组合
  • 若创建消息队列,key可指定键值,也可将之设置为IPC_PRIVATE。若打开进行查询,则key不能为0必须是一个非0的值否则是查询不到的
消息队列控制

int msgctl(int msgid,int cmd,struct msqid_ds *buf)

  • 返回:成功返回0,出错返回-1
  • 头文件
#include <sys/msg.h>
  • 参数

    • msgid:消息队列的id
    • buf:消息队列属性指针
    • cmd:

      • IPC_STAT:获取消息队列属性,取此队列的msqid_ds结构,并将其存放在buf指向的结构体中
      • IPC_SET:设置属性,按由buf指向的结构体中的值,设置此队列相关的结构中的字段
      • IPC_RMID:删除队列,从系统中删除该消息队列以及仍在该队列上的所有数据
发送消息

int msgsnd(int msgqid,const void* ptr,size_t nbytes,int flag);

  • 返回:成功返回0,出错返回-1
  • ptr 结构
struct mymesg{
        long mytype; // positive message type
        char mtext[512]; // message data of length nbytes
...
};
  • nbytes:指定消息的大小,不包括mtype的大小
  • mtype是指消息的类型,他由一个整数来代表,并且他只可能是大于0的整数
  • mtext是消息数据本身
  • 在linux中消息的最大长度是4056个字节,其中包括mtype,他占四个字节
  • 结构体mymesg用户可自定义,但第一个成员必须是mtype
  • 参数flag

    • 0:阻塞
    • IPC_NOWAIT:类似于文件I/O的非阻塞I/O标志
    • 若消息队列已满(或是队列中消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。如果指定0,则进程

      • 阻塞直到有空间可以容纳要发送的消息
      • 或从系统中删除了此队列
      • 或捕捉到了一个信号,并从信号处理程序返回
接受消息

size_t msgrcv(int msgqid,void* ptr,size_t nbytes,long type,int flag);

  • 返回:成功返回消息的数据部分长度,出错返回-1
  • 头文件
#include <sys/msg.h>
  • 参数

    • msgqid:消息队列id
    • ptr:指向存放消息的缓存
    • nbytes:消息缓存的大小,不包括mtype的大小。计算方式

      • nbytes = sizeof(struct mymesg) - sizeof(long)
    • type:消息类型

      • type == 0:获取队列消息中的第一个消息
      • type > 0:获取消息队列中类型为type的第一个消息
      • type < 0:获取消息队列中类型小于或等于type绝对值的消息(有多个就取类型最小的一个)
    • flag:0或IPC_NOWAIT

消息示例

#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
void send_message(int);
void receive_message(int);
/* 消息结构体,注意第一个元素必须是type */
typedef struct{
        long type; // 必须大于1,否则发送消息失败
        int id;
        char name[8];
}my_message;
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        // 可以用ftok生成key,注意argv[2]必须是一个实际存在的路径,不然函数返回-1
        // key_t k = ftok(argv[2],0);
        // 直接使用数字 
        key_t k = atoi(argv[2]);
        // 打开/创建消息队列
        int msgqid = 0;
        if((msgqid = msgget(k,IPC_CREAT | 0777)) == -1)
        {
                perror("msgget error");
                return 1;
        }
        if(strcmp("send",argv[1]) == 0)
        {
                send_message(msgqid);
        }
        else if(strcmp("receive",argv[1]) == 0)
        {
                receive_message(msgqid);
        }
        else
        {
                printf("incorrect parameter\n");
                return 1;
        }
        // 删除队列,程序结束后消息队列还是会存在,不用一定要接的删除
        if(msgctl(msgqid,IPC_RMID,NULL) == -1)
        {
                perror("remove message queue error");
                return 1;
        }
        return 0;
}
/* 发送消息方法 */
void send_message(int msgqid)
{
        my_message m = {0,0,{'\0'}};
        int type = 1;
        // 消息的大小,注意要减去type的大小
        size_t bytes = sizeof(my_message) - sizeof(long);
        struct msqid_ds ds;
        int c;
        while(1)
        {
                printf("input id name\n");
                scanf("%d %7s",&m.id,m.name);
                m.type = type;
                if(msgsnd(msgqid,(void*)&m,bytes,0) == -1)
                {
                        perror("msgsnd error");
                        break;
                }
                type++;
                if(-1 == msgctl(msgqid,IPC_STAT,&ds))
                {
                        perror("msgctl error");
                }
                else
                {
                        printf("msgqnum = %u\n",(unsigned int)ds.msg_qnum);
                }
                // 重置name字符数组
                memset(m.name,'\0',8);
                // 清空标准输入的缓冲区的内容
                while ((c=getchar()) != '\n' && c!=EOF);
        }
}
/* 接受消息方法 */
void receive_message(int msgqid)
{
        my_message m;
        // 消息的大小,注意要减去type的大小
        size_t bytes = sizeof(my_message) - sizeof(long);
        struct msqid_ds ds;
        while(1)
        {
                if(msgrcv(msgqid,(void*)&m,bytes,0,0) == -1)
                {
                        perror("msgrcv error");
                        break;
                }
                else
                {
                        printf("type = %d;id = %d;name = %s\n",(int)m.type,m.id,m.name);
                }
                if(-1 == msgctl(msgqid,IPC_STAT,&ds))
                {
                        perror("msgctl error");
                }
                else
                {
                        printf("msgqnum = %u\n",(unsigned int)ds.msg_qnum);
                }
        }
}

共享内存

  • 共享内存区域是被多个进程共享的一部分物理内存
  • 多个进程都可把该共享内存映射到自己的虚拟内存空间。所有用户空间的进程若要操作共享内存,都要将其映射到自己的虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信
  • 共享内存是进程间共享数据的一种最快的方法,一个进程向内存区域写入数据,共享这个区域的所有内存就可以立刻看到其中的内容
  • 本身不提供同步机制,可通过信号量进行同步
  • 提高数据处理效率,一种效率最高的IPC机制
共享内存属性
struct shmid_ds{
        struct ipc_perm shm_perm;
        size_t shm_segsz;// size of segment in bytes 共享内存区大小
        pid_t shm_lpid;// pid of last shmop 最后一次操作共享内存的进程id
        pid_t shm_cpid;// pid of creator 创建共享内存的进程的id
        shmatt_t shm_nattch;// number of current attaches 和当前共享内存成功映射的内存数量
        time_t shm_atime;// last attach time 最后成功映射的时间
        time_t shm_dtime;// last detach time 最后解除映射的时间
        time_t shm_ctime;// last change time 最后一次改变的时间
......
};
共享内存使用步骤
  • 使用shmget函数创建共享内存
  • 使用shmat函数映射共享内存,将这段创建的共享内存映射到具体的进程虚拟空间中
创建共享内存

int shmget(key_t key,size_t size,int shmflg);

  • 返回:成功返回内核中共享内存的标识id,失败返回-1
  • 头文件
#include <sys/shm.h>
  • 参数

    • key:用户指定的共享内存键值
    • size:共享内存大小
    • shmflg:IPC_CREAT,IPC_EXCL等权限组合
  • errno

    • EINVAL(无效的内存段大小)
    • EEXIST(内存段已经存在,无法创建)
    • EIDRM(内存段已经被删除)
    • ENOENT(内存段不存在)
    • EACCES(权限不够)
    • ENOMEM(没有足够的内存来创建内存段)
共享内存控制

int shmctl(int shmid,int cmd,struct shmid_ds* buf);

  • 返回:成功返回0,出错返回-1
  • 头文件
#include <sys/shm.h>
  • 参数

    • shmid:共享内存id
    • buf:共享内存属性指针
    • cmd

      • IPC_STAT 获取共享内存段属性
      • IPC_SET 设置共享内存段属性
      • IPC_RMID 删除共享内存段
      • SHM_LOCK 锁定共享内存段页面(页面映射到的物理内存不和外存(swap)进行换入换出操作)
      • SHM_UNLOCK 解除共享内存段页面的锁定
共享内存映射和接触映射

void* shmat(int shmid,char* shmaddr,int shmflg);

  • 返回:成功返回共享内存映射到虚拟内存的空间的地址,失败返回-1
    int shmdt(char* shmaddr);
  • 返回:失败返回-1
  • 参数

    • shmid:共享内存id
    • shmaddr:映射到进程虚拟内存空间的地址,建议设置0,有操作系统分配
    • shmflg:若shmaddr设置成0,则shmflg也设置成0

      • SHM_RND
      • SHMLBA 地址为2的乘方
      • SHM_RDONLY 只读方式链接
  • errno

    • EINVAL 无效的IPC ID值或无效的地址
    • ENOMEM 没有足够的内存
    • EACCES 存取权限不够
      子进程不继承父进程创建的共享内存,大家的是共享的。子进程继承父进程映射的地址
示例
#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>
// 存放pipe,用来使进程互斥
int fp[2];
int init_pipe(void);
void close_pipe(void);
void sleep_pipe(void);
void wake_pipe(void);
int main(void)
{
        // 创建虚拟内存块
        key_t k = 888;
        int shmid;
        if((shmid = shmget(k,sizeof(int),IPC_CREAT | 0777)) == -1)
        {
                perror("shmget error");
                return 1;
        }
        // 映射共享内存,可直接在fork映射也可以在fork后映射
        /*
        int* share_p = shmat(shmid,NULL,0);
        if((int)share_p == -1)
        {
                perror("shmat error");
                return 1;
        }
        */
        // 初始化pipe
        init_pipe();
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                return 1;
        }
        if(pid > 0)
        {
                // 映射共享内存
                int* share_p = shmat(shmid,NULL,0);
                if((int)share_p == -1)
                {
                        perror("shmat error");
                        return 1;
                }
                *share_p = 8;
                wake_pipe();
                wait(NULL);
                // 接触共享内存映射
                shmdt(share_p);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
        }
        else if(pid == 0)
        {
                // 映射共享内存
                int* share_p = shmat(shmid,NULL,0);
                if((int)share_p == -1)
                {
                        perror("shmat error");
                        return 1;
                }
                sleep_pipe();
                printf("*share_p = %d\n",*share_p);
                // 接触共享内存映射
                shmdt(share_p);
        }
        // 关闭pipe
        close_pipe();
        return 0;
}
int init_pipe(void)
{
        return pipe(fp);
}
void close_pipe(void)
{
        close(fp[0]);
        close(fp[1]);
}
void sleep_pipe(void)
{
        char c;
        read(fp[0],(void*)&c,sizeof(c));
}
void wake_pipe(void)
{
        char c = 'D';
        write(fp[1],(void*)&c,sizeof(c));
}

信号量

  • 本质上就是共享资源的数目,用来控制对共享资源的访问
  • 用于进程间的互斥和同步
  • 每种共享资源对应一个信号量,为了便于大量共享资源的操作引入了信号集,可对所有信号量一次性操作。对信号量集中多有操作可以要求全部成功也可以部分成功
  • 二元信号量(信号灯)值为0和1
  • 对信号量作PV操作
信号量集属性
#include <sys/sem.h>
struct semid_ds{
        struct ipc_perm sem_perm;
        unsigned short sem_nsems;// of semaphores in set 信号量集中灯的数量
        time_t sem_otime; // last semop time 最后一次对信号量操作的时间
        time_t sem_ctime;// last change time 最后一次改变时间
};
创建信号量集

int semget(key_t key,int nsems,int flag);

  • 返回:成功返回信号量集id,出错返回-1
  • 参数

    • key:用户指定的信号量集键值
    • nsems:信号量集中信号量个数
    • flag:IPC_CREAT,IPC_EXCL等权限组合
  • 头文件
#include <sys/sem.h>
信号量集控制

int semctl(int semid,int semnum,int cmd,.../* union semun arg */);

  • union semun结构
// 需要自己定义
union semun{
        int val;
        struct semid_ds* buf;
        unsigned short* array;
};
  • 参数

    • semid:信号量集id
    • semnum:0表示对所有信号量操作,信号量编号从0开始
    • val:放置获取或设置信号量集中某个信号量的值
    • buf:信号量集属性指针
    • array:放置获取或设置信号量集中所有信号量的值
    • 通过cmd参数设定对信号量集要执行的操作

      • IPC_STAT 获取信号量集的熟悉 ==> buf
      • IPC_SET 设置信号量集的熟悉 ==> buf
      • IPC_RMID 删除信号量集 ==> buf
      • GETVAL 返回信号量的值 ==> val
      • SETVAL 设置semnum信号量的值 ==> val
      • GETALL 获取所有信号量的值 ==> array
      • SETALL 设置所有信号量的初始值 ==> array
  • 头文件
#include <sys/sem.h>
信号量集操作

int semop(int semid,struct sembuf* sops,size_t nsops);

  • 返回:成功返回0,出错返回-1
  • struct sembuf 结构体
struct sembuf{
        unsigned short sem_num;// member in set 对哪一个信号量进行操作
        short sem_op;// operation(negative,0,positive) 做P操作还V操作
        short sem_flg;// IPC_NOWAIT,SEM_UNDO
};
  • 参数

    • semid:信号量集id
    • sops:sembuf结构体数组指针
    • nsops:第二个参数中结构体数组的长度
    • sem_num:信号量集中信号量的编号
    • sep_op:正数为V操作,复数为P操作,0可用于对共享资源是否已完成的测试
    • sem_flg:SEM_UNDO标志,表示在进程结束时,相应的操作将被取消,如果设置了该标志,那么在进程没有释放共享资源就退出时,内核将代为释放
  • 用于信号量集中信号量的加和减操作(PV操作)
  • 可用于进程间的互斥和同步
银行取款,PV操作
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct
{
        int deposit;
        int semid;
}Account;
typedef union
{
        int val;
        struct semid_ds* buf;
        unsigned short* array;
}my_semun;
int I(void);
void P(int semid);
void V(int semid);
void D(int semid);
void withdraw(Account* account,int amount);
int main(void)
{
        key_t k = 888;
        int shmid = shmget(k,sizeof(Account),IPC_CREAT | 0777);
        if(shmid == -1)
        {
                perror("shmget error");
                return 1;
        }
        // 映射
        Account* account = shmat(shmid,0,0);
        account->deposit = 888888;
        account->semid = I();
        V(account->semid);
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                // 解除映射
                shmdt(account);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
                // 删除信号量集
                D(account->semid);
                return 1;
        }
        else if(pid > 0)
        {
                withdraw(account,888888);
                wait(NULL);
                // 删除信号量集
                D(account->semid);
                // 解除映射
                shmdt(account);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
        }
        else if(pid == 0)
        {
                withdraw(account,888888);
                // 解除映射
                shmdt(account);
        }
        return 0;
}
void withdraw(Account* account,int amount)
{
        // 进行P操作,其他进程会阻塞
        P(account->semid);
        if(amount <= account->deposit)
        {
                account->deposit -= amount;
        }
        printf("process %d withdraw\n",(int)getpid());
        sleep(3);
        // 使其他进程继续运行
        V(account->semid);
}
int I(void)
{
        // 创建信号量集
        int semid = semget(IPC_PRIVATE,1,IPC_CREAT | 0777);
        if(semid != -1)
        {

                my_semun val;
                val.val = 0;
                // 初始化信号量集
                semctl(semid,0,SETALL,&val);
        }
        return semid;
}
void P(int semid)
{
        // 使用sembuf结构体进行P操作
        struct sembuf buf[1] = {{0,-1,SEM_UNDO}};
        semop(semid,buf,1);
}
void V(int semid)
{
        // 使用sembuf结构体进行V操作
        struct sembuf buf[1] = {{0,1,SEM_UNDO}};
        semop(semid,buf,1);
}
void D(int semid)
{
        // 删除信号量集
        semctl(semid,0,IPC_RMID,NULL);
}
读者和写者问题
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct
{
        int c_semid,g_semid;
        int* data;
}Calculate;
typedef union
{
        int val;
        struct semid_ds* buf;
        unsigned short* array;
}my_semun;
int I(void);
void get_v(Calculate*);
void calculate_v(Calculate*);
void P(int semid);
void V(int semid);
void D(int semid);
int main(void)
{
        key_t k = 888;
        int shmid = shmget(k,sizeof(int),IPC_CREAT | 0777);
        if(shmid == -1)
        {
                perror("shmget error");
                return 1;
        }
        // 映射
        Calculate c;
        c.data = shmat(shmid,0,0);
        c.c_semid = I();
        c.g_semid = I();
        int pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                // 解除映射
                shmdt(c.data);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
                // 删除信号量集
                D(c.c_semid);
                D(c.g_semid);
                return 1;
        }
        else if(pid > 0)
        {
                get_v(&c);
                wait(NULL);
                // 删除信号量集
                D(c.c_semid);
                D(c.g_semid);
                // 解除映射
                shmdt(c.data);
                // 删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
        }
        else if(pid == 0)
        {
                calculate_v(&c);
                // 解除映射
                shmdt(c.data);
        }
        return 0;
}
void get_v(Calculate* c)
{
        for(int i = 0;i < 100;i++)
        {
                printf("get; i = %d\n",*c->data);
                *c->data = i;
                V(c->c_semid);
                P(c->g_semid);
                printf("print; i = %d\n--------\n\n",*c->data);
        }
}
void calculate_v(Calculate* c)
{
        for(int i = 0;i < 100;i++)
        {
                P(c->c_semid);
                *c->data += 8;
                printf("calculate; i = %d\n",*c->data);
                V(c->g_semid);
        }
}
int I(void)
{
        // 创建信号量集
        int semid = semget(IPC_PRIVATE,1,IPC_CREAT | 0777);
        if(semid != -1)
        {

                my_semun val;
                val.val = 0;
                // 初始化信号量集
                semctl(semid,0,SETALL,&val);
        }
        return semid;
}
void P(int semid)
{
        // 使用sembuf结构体进行P操作
        struct sembuf buf[1] = {{0,-1,SEM_UNDO}};
        semop(semid,buf,1);
}
void V(int semid)
{
        // 使用sembuf结构体进行V操作
        struct sembuf buf[1] = {{0,1,SEM_UNDO}};
        semop(semid,buf,1);
}
void D(int semid)
{
        // 删除信号量集
        semctl(semid,0,IPC_RMID,NULL);
}

linux c内存分配方式以及动态内存分配

从静态存储区分配(由系统控制)

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,staic变量

在栈上分配(由系统控制)

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。效率很高,凡事分配的内存容量有限

在堆上分配,亦称动态内存分配(由自己控制)

程序在运行时用malloc等函数申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生命周期有我们自己决定,使用非常灵活,但问题也最多

malloc函数

void *malloc(size_t size);

  • 使用:需要包含malloc.h头文件
  • 功能:允许从空闲内存池中分配连续内存但不初始化
  • 参数:size参数是一个所需字节
  • 返回:若分配成功则返回一个指向该内存块的指针,在使用时可根据需要做强制类型转换,否则返回NULL指针
calloc函数

void *calloc(size_t num_elements,size_t element_size);

  • 使用:需要包含malloc.h头文件
  • 功能:功能与malloc函数相同,但作初始化
  • 参数:num_elements是所需元素的数量,element_size是每个元素的字节
  • 返回:与malloc相同
realloc含糊

void *realloc(void *ptr,size_t new_size)

  • 使用:需要包含malloc.h头文件
  • 功能:在指针ptr指向的内存基础上扩大或缩小内存
  • 参数:ptr是指向先前通过malloccallocrealloc函数后分配的内存块的指针,new_size是内存块的新尺寸,可能大于或小于原有内存尺寸
  • 返回:若能调整内存大小则返回指向调整后内存的指针,否则返回NULL
realloc函数的使用注意点
  • 当扩展内存时,不会对添加的内存块的字节进行初始化
  • 弱不能调整内存则返回NULL,但原有内存中的数据不会发生改变
  • 若第一个参数为NULL那么功能等同于malloc函数,若第二个参数为0,那么会释放调用内存块。
  • 在缩小或扩大内存时一般不会对其进行移动,若无法扩大内存块(内存块后面的字节已经用于其他木的),那么可能会在别处分配心的内存块,然后把旧块的数据复制到新块中,并将旧块删除释放内存
free函数

void free(void *ptr);

  • 功能:释放由指针ptr指向的动态内存分配的内存块
free函数注意点
  • 如果ptrNULL指针,那么freeptr无论操作多少次都不会出问题
  • 如果ptr不是NULL指针,那么freeptr连续操作两次就会导致程序停止运行

示例

// stdlib会包含malloc
#include <stdlib.h>
#include <stdio.h>
void print_arr(int *p,int length);
int main(void)
{
        size_t length = 0;
        scanf("%d",&length);
        if(length > 0)
        {
                // 使用malloc分配内存
                //int *p = (int*)malloc(length);
                // 使用calloc分配内存
                int *p = (int*)calloc(sizeof(int),length);
                p[0] = 8;
                print_arr(p,length);
                // 使用 realloc修改申请的内存空间的大小
                p = realloc(p,length + 2);
                print_arr(p,length + 2);
                free(p);
                // 注意虽然释放了申请的内存,但是这个注意要清空下指针
                p = NULL;
        }
        return 0;
}
void print_arr(int *p,int length)
{
        for(int i = 0;i < length;i++)
        {
                printf("p[%d] = %d;\n",i,p[i]);
        }
}

超级猜图swift

代码太多,贴几个重点吧

通过plist初始化(kvc),可以很方便的对类的属性进行赋值
//
//  Question.swift
//  超级猜图
//
//  Created by admin on 15/12/30.
//  Copyright © 2015年 jin. All rights reserved.
//

import UIKit

class Question: NSObject {
    var answer:NSString!
    var icon:NSString!
    var title:NSString!
    var options:NSArray!
    //加载文件,初始化对象数组
    class func loadQuestionOfFile()->[Question]
    {
        let datas:NSArray! = NSArray(contentsOfFile: NSBundle.mainBundle().pathForResource("questions", ofType: "plist")!)
        var temp:[Question] = []
        for data in datas
        {
            temp.append(Question.instance(data as! NSDictionary))
        }
        return temp
    }
    //传入字典,初始化对象
    class func instance(questionData:NSDictionary)->Question
    {
//        var temp = Question(answer:questionData["answer"] as! NSString,title:questionData["title"] as! NSString,icon:questionData["icon"] as! NSString,option:questionData["option"] as! NSArray)
//        self.init(answer:questionData["answer"] as! NSString,title:questionData["title"] as! NSString,icon:questionData["icon"] as! NSString,option:questionData["options"] as! NSArray)
        let temp = Question()
        //直接使用字典的键值对队完成对对象属性的肤赋值
        temp.setValuesForKeysWithDictionary(questionData as! [String : AnyObject])
        return temp
    }
}

kvc其它用法
//取出对象数组中对象的icon值
print((self.questions as NSArray).valueForKeyPath("icon")!)
//取出对象数组中对象的book值(是一个类)的name属性,这里执行会报错,oc使用点分割swift呢?
print(self.questions[0].valueForKeyPath("book.name"))
隐藏程序界面状态栏
override func prefersStatusBarHidden() -> Bool {
     return true
}
隐藏启动界面状态栏

General>Deployment Info>Hide status bar勾选就好了

修改状态栏 style
override func preferredStatusBarStyle() -> UIStatusBarStyle {
    return UIStatusBarStyle.LightContent//居中
}
删除子控件
self.answerButtonView.subviews.forEach({$0.removeFromSuperview()})
弹出框
var alert = UIAlertView(title: "提示", message: "后面没有题了", delegate: nil, cancelButtonTitle: "取消", otherButtonTitles: "确认", "测试A","测试B")
控件放到最顶层
self.view.bringSubviewToFront(self.imageView)

使用Cocapods管理开源库

1.初始化pod,进入到项目的目录下执行

pod init

执行完成之后会出现Podfile文件
2.编辑Podfile文件

# Uncomment this line to define a global platform for your project
# platform :ios, '8.0'
# Uncomment this line if you're using Swift
# use_frameworks!

target 'MagicBox' do
platform:ios,'7.0'#所有库支持的ios最低版本
pod 'AFNetworking','~>2.3.1'#开源库版本号
pod 'SVProgressHUD','~>1.0'
pod 'Cordova','~>3.5.0'
pod 'GCJSONKit','~>1.5.0'
pod 'CordovaPlugin-console','~>0.2.10'
pod 'CordovaPlugin-device','~>0.2.11'
pod 'CordovaPlugin-network-information','~>0.2.3'
end

target 'MagicBoxTests' do

end

target 'MagicBoxUITests' do

end

3.安装开源库

pod install

执行成功之后会显示

Updating local specs repositories
Analyzing dependencies
Downloading dependencies
Installing AFNetworking (2.3.1)
Installing Cordova (3.5.0)
Installing CordovaPlugin-console (0.2.10)
Installing CordovaPlugin-device (0.2.11)
Installing CordovaPlugin-network-information (0.2.3)
Installing GCJSONKit (1.5.0)
Installing SVProgressHUD (1.1.3)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `DemoApp.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There are 7 dependencies from the Podfile and 7 total pods installed.

4.打开创建的DemoApp.xcworkspace,在桥接文件里面加入引入库文件的代码即可

#import "JSONKit.h"
#import "SVProgressHUD.h"
#import "CDV.h"
#import "AFNetworking.h"
#import "CDVLogger.h"
#import "CDVDevice.h"
#import "CDVConnection.h"

php运行模式

PHP运行模式有4钟:

  1. cgi 通用网关接口(Common Gateway Interface))
  2. fast-cgi 常驻 (long-live) 型的 CGI
  3. cli 命令行运行 (Command Line Interface)
  4. web模块模式 (apache等web服务器运行的模块模式)

1. CGI(Common Gateway Interface)

CGI即通用网关接口(Common Gateway Interface),它是一段程序, 通俗的讲CGI就象是一座桥,把网页和WEB服务器中的执行程序连接起来,它把HTML接收的指令传递给服务器的执行程序,再把服务器执行程序的结果返还给HTML页。CGI 的跨平台性能极佳,几乎可以在任何操作系统上实现。 CGI已经是比较老的模式了,这几年都很少用了。
每有一个用户请求,都会先要创建cgi的子进程,然后处理请求,处理完后结束这个子进程,这就是fork-and-execute模式。 当用户请求数量非常多时,会大量挤占系统的资源如内存,CPU时间等,造成效能低下。所以用cgi方式的服务器有多少连接请求就会有多少cgi子进程,子进程反复加载是cgi性能低下的主要原因。
如果不想把 PHP 嵌入到服务器端软件(如 Apache)作为一个模块安装的话,可以选择以 CGI 的模式安装。或者把 PHP 用于不同的 CGI 封装以便为代码创建安全的 chroot 和 setuid 环境。这样每个客户机请求一个php文件,Web服务器就调用php.exe(win下是php.exe,linux是php)去解释这个文件,然后再把解释的结果以网页的形式返回给客户机。 这种安装方式通常会把 PHP 的可执行文件安装到 web 服务器的 cgi-bin 目录。CERT 建议书 CA-96.11 建议不要把任何的解释器放到 cgi-bin 目录。

这种方式的好处是把web server和具体的程序处理独立开来,结构清晰,可控性强,同时缺点就是如果在高访问需求的情况下,cgi的进程fork就会成为很大的服务器负担,想 象一下数百个并发请求导致服务器fork出数百个进程就明白了。这也是为什么cgi一直背负性能低下,高资源消耗的恶名的原因。

CGI模式安装:

CGI已经是比较老的模式了,这几年都很少用了,所以我们只是为了测试。

安装CGI模式需要注释掉
LoadModule php5_module modules/libphp5.so
这行。如果不注释这行会一直走到handler模式。也就是模块模式。

然后在httpd.conf增加action:

Action application/x-httpd-php /cgi-bin/

如果在/cgi-bin/目录找不到php-cgi.可自行从php的bin里面cp一个。

然后重启apache,再打开测试页面发现Server API变成:CGI/FastCGI。说明成功切换为cgi模式。

问题:

1) 如果cgi程序放在/usr/local/httpd/cgi-bin/里无法执行,遇到403或500错误的话

打开apache错误日志 有如下提示: Permission denied: exec of

可以检查cgi程序的属性,按Linux contexts文件 里定义的,/usr/local/httpd/cgi-bin/里必须是httpd_sys_script_exec_t 属性。 通过ls -Z查看,如果不是则通过如下命令更改: chcon -t httpd_sys_script_exec_t /var/www/cgi-bin/*.cgi 如果是虚拟主机里的cgi,则参考问题2使之能正常使用普通的功能后,再通过chcon设置cgi文件的context为

httpd_sys_script_exec_t即可。chcon -R -t httpd_sys_script_exec_t cgi-bin/

2) apache错误提示:.... malformed header from script. Bad header=

根据提示说明有header有问题,查看文件输出的第一句话是什么,应该类似于如下

Content-type: text/plain; charset=iso-8859-1\n\n

或者Content-type:text/html\n\n

注意:声明好Content-type后要输出两个空行。

3)apache错误提示: Exec format error

脚本解释器设置错误。脚本第一行应该以'#!解释器路径'的形式, 填写脚本解释器的路径,如果是PERL程序,常见的路径为: #!/usr/bin/perl 或 #!/usr/local/bin/perl 如果是PHP程序,不需要填写解释器路径,系统会自动找到PHP。

2. Fastcgi模式

fast-cgi 是cgi的升级版本,FastCGI 像是一个常驻 (long-live) 型的 CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去 fork 一次 (这是 CGI 最为人诟病的 fork-and-execute 模式)。
FastCGI的工作原理是:

(1)、Web Server启动时载入FastCGI进程管理器【PHP的FastCGI进程管理器是PHP-FPM(php-FastCGI Process Manager)】(IIS ISAPI或Apache Module);

(2)、FastCGI进程管理器自身初始化,启动多个CGI解释器进程 (在任务管理器中可见多个php-cgi.exe)并等待来自Web Server的连接。

(3)、当客户端请求到达Web Server时,FastCGI进程管理器选择并连接到一个CGI解释器。Web server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi。

(4)、FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在 WebServer中)的下一个连接。在正常的CGI模式中,php-cgi.exe在此便退出了。

在CGI模式中,你可以想象 CGI通常有多慢。每一个Web请求PHP都必须重新解析php.ini、重新载入全部dll扩展并重初始化全部数据结构。使用FastCGI,所有这些都只在进程启动时发生一次。一个额外的好处是,持续数据库连接(Persistent database connection)可以工作。

Fastcgi的优点:

1)从稳定性上看, fastcgi是以独立的进程池运行来cgi,单独一个进程死掉,系统可以很轻易的丢弃,然后重新分 配新的进程来运行逻辑.

2)从安全性上看,Fastcgi支持分布式运算. fastcgi和宿主的server完全独立, fastcgi怎么down也不会把server搞垮.

3)从性能上看, fastcgi把动态逻辑的处理从server中分离出来, 大负荷的IO处理还是留给宿主server, 这样宿主server可以一心一意作IO,对于一个普通的动态网页来说, 逻辑处理可能只有一小部分, 大量的图片等静态

FastCGI缺点:说完了好处,也来说说缺点。从我的实际使用来看,用FastCGI模式更适合生产环境的服务器。但对于开发用机器来说就不太合适。因为当使用 Zend Studio调试程序时,由于 FastCGI会认为 PHP进程超时,从而在页面返回 500错误。这一点让人非常恼火,所以我在开发机器上还是换回了 ISAPI模式。

安装fastcgi模式:
安装apache路径是/usr/local/httpd/

安装php路径是/usr/local/php/

1)安装mod_fastcgi

wget http://www.fastcgi.com/dist/mod_fastcgi-2.4.6.tar.gz

tar zxvf mod_fastcgi-2.4.6.tar.gz

cd mod_fastcgi-2.4.6

cp Makefile.AP2 Makefile

vi Makefile,编辑top_dir = /usr/local/httpd

make

make install

安装完后,

/usr/local/httpd/modules/多出一个文件:mod_fcgid.so

2)重新编译php

./configure --prefix=/usr/local/php --enable-fastcgi --enable-force-cgi-redirect --disable-cli

make

make install

这样编译后,在PHP的bin目录下的php-cgi就是fastcgi模式的php解释器了

安装成功后,执行

php -v 输出

PHP 5.3.2 (cgi-fcgi).

这里输出带了cgi-fcgi

注意:

  1. 编译参数不能加 –with-apxs=/usr/local/httpd/bin/apxs 否则安装出来的php执行文件是cli模式的

  2. 如果编译时不加--disable-cli则输出 PHP 5.3.2(cli)

3)配置apache

需要配置apache来以fastcgi模式运行php程序

vi httpd.conf

我们使用虚拟机的方式实现:

加载fastcgi模块

LoadModule fastcgi_module modules/mod_fastcgi.so

//以静态方式执行fastcgi 启动了10进程

FastCgiServer /usr/local/php/bin/php-cgi -processes 10 -idle-timeout 150 -pass-header HTTP_AUTHORIZATION

<VirtualHost *:80>

     #

     DocumentRoot   /usr/local/httpd/fcgi-bin   

     ServerName www.fastcgitest.com



     ScriptAlias /fcgi-bin/   /usr/local/php/bin/   #定义目录映射 /fcgi-bin/ 代替 /usr/local/php/bin/

     Options +ExecCGI

     AddHandler fastcgi-script .php .fcgi         #.php结尾的请求都要用php-fastcgi来处理  

     AddType application/x-httpd-php .php     #增加MIME类型

     Action application/x-httpd-php /fcgi-bin/php-cgi  #设置php-fastcgi的处理器: /usr/local/php/bin/php-cgi

 <Directory /usr/local/httpd/fcgi-bin/>

      Options Indexes ExecCGI

      Order allow,deny

      allow from all

 </Directory>

</VirtualHost>

或者

<IfModule mod_fastcgi>ScriptAlias /fcgi-bin/ "/usr/local/php/bin" #定义目录映射FastCgiServer /usr/local/php/bin/php-cgi   -processes 10 #配置fastcgi server,<Directory "/usr/local/httpd/fcgi-bin/">SetHandler fastcgi-scriptOptions FollowSymLinksOrder allow,denyAllow from all</Directory>AddType application/x-httpd-php .php  #增加MIME类型AddHandler php-fastcgi .php   #.php结尾的请求都要用php-fastcgi来处理Action php-fastcgi /fcgi-bin/php-cgi #设置php-fastcgi的处理器
</IfModule>

4).restart 下apache,查看phpinfo,如果服务器信息是:

Apache/2.2.11 (Unix) mod_fastcgi/2.4.6之类的就说明安装成功了。

如果出现403的错误,查看下/usr/local/httpd/fcgi-bin/是否有足够的权限。

或者

Options FollowSymLinks

AllowOverride None

Order deny,allow

Deny from all

改为:

<Directory />

    Options FollowSymLinks

    AllowOverride None

    Order allow,deny

    Allow from all

</Directory>

就可以了。

ps -ef|grep php-cgi可以看见10个fastcgi进程在跑。

3. CLI模式

cli是php的命令行运行模式,大家经常会使用它,但是可能并没有注意到(例如:我们在linux下经常使用 "php -m"查找PHP安装了那些扩展就是PHP命令行运行模式;有兴趣的同学可以输入php -h去深入研究该运行模式)

1.让 PHP 运行指定文件。

php script.php

php -f script.php

以上两种方法(使用或不使用 -f 参数)都能够运行脚本的script.php。您可以选择任何文件来运行,您指定的 PHP 脚本并非必须要以 .php 为扩展名,它们可以有任意的文件名和扩展名。

2.在命令行直接运行 PHP 代码。

php -r "print_r(get_defined_constants());"

在使用这种方法时,请您注意外壳变量的替代及引号的使用。

注: 请仔细阅读以上范例,在运行代码时没有开始和结束的标记符!加上 -r 参数后,这些标记符是不需要的,加上它们会导致语法错误。

3.通过标准输入(stdin)提供需要运行的 PHP 代码。

以上用法给我们提供了非常强大的功能,使得我们可以如下范例所示,动态地生成 PHP 代码并通过命令行运行这些代码:

$ some_application | some_filter | php | sort -u >final_output.txt

4. 模块模式

模块模式是以mod_php5模块的形式集成,此时mod_php5模块的作用是接收Apache传递过来的PHP文件请求,并处理这些请求,然后将处理后的结果返回给Apache。如果我们在Apache启动前在其配置文件中配置好了PHP模块(mod_php5), PHP模块通过注册apache2的ap_hook_post_config挂钩,在Apache启动的时候启动此模块以接受PHP文件的请求。

除了这种启动时的加载方式,Apache的模块可以在运行的时候动态装载,这意味着对服务器可以进行功能扩展而不需要重新对源代码进行编译,甚至根本不需要停止服务器。我们所需要做的仅仅是给服务器发送信号HUP或者AP_SIG_GRACEFUL通知服务器重新载入模块。但是在动态加载之前,我们需要将模块编译成为动态链接库。此时的动态加载就是加载动态链接库。 Apache中对动态链接库的处理是通过模块mod_so来完成的,因此mod_so模块不能被动态加载,它只能被静态编译进Apache的核心。这意味着它是随着Apache一起启动的。

Apache是如何加载模块的呢?我们以前面提到的mod_php5模块为例。首先我们需要在Apache的配置文件httpd.conf中添加一行:

该运行模式是我们以前在windows环境下使用apache服务器经常使用的,而在模块化(DLL)中,PHP是与Web服务器一起启动并运行的。(是apache在CGI的基础上进行的一种扩展,加快PHP的运行效率)

LoadModule php5_module modules/mod_php5.so

这里我们使用了LoadModule命令,该命令的第一个参数是模块的名称,名称可以在模块实现的源码中找到。第二个选项是该模块所处的路径。如果需要在服务器运行时加载模块,可以通过发送信号HUP或者AP_SIG_GRACEFUL给服务器,一旦接受到该信号,Apache将重新装载模块,而不需要重新启动服务器。

5. php在Nginx中运行模式(Nginx+ PHP-FPM)

使用FastCGI方式现在常见的有两种stack:ligthttpd+spawn-fcgi;另外一种是nginx+PHP-FPM(也可以用spawn-fcgi)。

A、如上面所说该两种结构都采用FastCGI对PHP支持,因此HTTPServer完全解放出来,可以更好地进行响应和并发处理。因此lighttpd和nginx都有small, but powerful和efficient的美誉。

B、该两者还可以分出一个好坏来,spawn-fcgi由于是lighttpd的一部分,因此安装了lighttpd一般就会使用spawn-fcgi对php支持,但是目前有用户说ligttpd的spwan-fcgi在高并发访问的时候,会出现上面说的内存泄漏甚至自动重启fastcgi。即:PHP脚本处理器当机,这个时候如果用户访问的话,可能就会出现白页(即PHP不能被解析或者出错)。

另一个:首先nginx不像lighttpd本身含带了fastcgi(spawn-fcgi),因此它完全是轻量级的,必须借助第三方的FastCGI处理器才可以对PHP进行解析,因此其实这样看来nginx是非常灵活的,它可以和任何第三方提供解析的处理器实现连接从而实现对PHP的解析(在nginx.conf中很容易设置)。nginx可以使用spwan-fcgi(需要一同安装lighttpd,但是需要为nginx避开端口,一些较早的blog有这方面安装的教程),但是由于spawn-fcgi具有上面所述的用户逐渐发现的缺陷,现在慢慢减少使用nginx+spawn-fcgi组合了。

C、由于spawn-fcgi的缺陷,现在出现了新的第三方(目前还是,听说正在努力不久将来加入到PHP core中)的PHP的FastCGI处理器,叫做PHP-FPM(具体可以google)。它和spawn-fcgi比较起来有如下优点:

由于它是作为PHP的patch补丁来开发的,安装的时候需要和php源码一起编译,也就是说编译到php core中了,因此在性能方面要优秀一些;

同时它在处理高并发方面也优于spawn-fcgi,至少不会自动重启fastcgi处理器。具体采用的算法和设计可以google了解。

因此,如上所说由于nginx的轻量和灵活性,因此目前性能优越,越来越多人逐渐使用这个组合:nginx+PHP/PHP-FPM

6. 总结

目前在

HTTPServer这块基本可以看到有三种stack比较流行:

(1)Apache+mod_php5

(2)lighttp+spawn-fcgi

(3)nginx+PHP-FPM

三者后两者性能可能稍优,但是Apache由于有丰富的模块和功能,目前来说仍旧是老大。有人测试nginx+PHP-FPM在高并发情况下可能会达到Apache+mod_php5的5~10倍,现在nginx+PHP-FPM使用的人越来越多。