Linux进程之间资源保护

说明

不同的进程如果需要访问同一资源,需要对资源的访问加上保护,保护的方法有 *互斥量,信号量,文件锁。

方法1:通过互斥量pthread_mutex_t保护

互斥量pthread_mutex_t本来用在线程中,有pthread_mutex_init,pthread_mutex_lock,pthread_mutex_unlock等函数构成资源保护。如果需要进程资源保护,需要使用pthread_mutexattr_t变量来设置互斥量的属性。
ret=pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
同时,需要使得不同进程能够访问这个互斥量,本例子通过mmap函数来实现:
g_mutex=(pthread_mutex_t*)mmap(NULL,sizeof(pthread_mutex_t),PROT_READ|PROT_WRITE,MAP_SHARED|
MAP_ANONYMOUS,-1,0);
g_mutex=(pthread_mutex_t*)mmap(NULL,sizeof(pthread_mutex_t),PROT_READ|
PROT_WRITE,MAP_SHARED,fd,0);
前面一个函数得到一个无名互斥量的地址,后面的函数得到一个有名互斥量地址。前者适用于有相关关系的进程,比如通过fork出来的线程,由于在同一个程序中,所以可以采用匿名映射。后者,适用于没有相关关系的进程,通过一个公认的文件获得一个映射地址,这样不同的进程可以取同样的映射内存

方法2:通过信号量来保护

Linux中信号量的实现有两种: XSI_SEM和 POSIX_SEM

-XSI信号量

XSI_SEM信号量一般用于进程之间通信,与POSIX_SEM相比 XSI_SEM有以下缺点:
1.获得状态与初始化分两步,不是原子化;
2.比较原始,使用比较复杂,但是通用;而 POSIX_SEM则是包装了,使用起来比较方便。
一般来说,信号量的使用包括以下几个步骤:

1.获得或创建信号量,信号量初始值为1,表示信号量可用
2.执行P操作,该操作首先测试信号量是否为1,如果为1,则信号量减一,执行后续操作。如果信号量为0,则阻塞
3.执行V操作,该操作将信号量+1,释放资源。如果此时有被阻塞的进程,则将获得资源继续进行下一步操作。

XSI_SEM信号量相关函数:
1. ftok关键字
key_t ftok(路径名,关键字)
成功返回0,失败返回-1
该函数获得一个以路径名和使用权限为关键词的唯一的序号以代表一个信号量的标识(不是信号量本身)
2. semget获得信号量:
int semget(关键词,期望获得的信号量数量,信号量属性)
成功返回信号量ID,失败返回-1
该函数获得一个根据关键词获得的一组信号量
关键词:就是通过ftok获得的key
数量:信号量集中的信号量数量;如果是0表示引用已有的信号量
信号量属性:
IPC_CREAT:表示是创建
IPC_EXCL:如果信号量已经存在,则当与IPC_CREAT联合使用时会返回EEXIST错误。
权限属性:同chmod
3. semctl:操纵信号量
semctl(semid,第几个信号,命令,命令参数)
成功返回0,失败返回-1
操作信号量的命令包括:
SETVAL 设置成员变量中的val
GETVAL 获得成员变量中的val
IPC_RMID:删除信号量
命令参数是一个联合
union semun{
int val;//for getval,setval
struct senid_ds*buf;//for ipc_start,ipc_set
unsigned short *array;//for getall,setall
struct seninfo *__buf;
};
4.semop:操作信号量:
semop(semid,信号量操作参数,操作参数中参数数量)
成功返回0,失败返回-1
操作参数是一个struct sembuf 结构
struc sembuf{
short sem_op;
unsigned short sem_num
short sem_flg;
}
sem_op:是操作1表示+1,-1表示减一,如果是负值,当前信号量不能满足要求时,则阻塞。
sem_num:表示操作的是信号集中的第几个,从0开始。
sem_flg:操作属性,可以是IPC_NOWAIT,当资源不可用时不阻塞,返回错误;
5.使用完信号量时,通过semctl调用IPC_RMID来删除该信号量。

-POSIX_SEM是另外一种信号量,与XSI_SEM相比,更容易使用。

POSIX_SEM信号量有有名和无名两种,前者可以在多个进程中共享,后者只能在单个进程中共享。
实际上有名 POSIX_SEM信号量是在/dev/shm目录下产生一个共享内存。

有名信号量创建:
sem_t *sem_open(信号名,创建属性,权限属性,初始值)
信号名:是一个字符串,最好在前面加一个/
创建属性:与open函数类似,
O_CREAT:表示是创建,如果不与O_EXCL联合使用,则当该信号量不存在时就创建,否则忽略第3,4个参数返回该信号量。
O_RDWR:读写
O_EXCL:独占,如果与 O_CREAT联合使用,则如果该信号量已经存在,调用sem open函数会返回错误。
权限属性:包括用户,组,其他用户的权限,同chmod
初始值:如果是二进制信号量1表示该信号量可用,0表示不可用。或者其他值当作计数器使用。
返回值:成功返回一个指向创建的信号量指针。
失败:SEM_FAILED
无名信号量创建:
int sem_init(信号量指针,是否进程共享,初始值)
是否共享:非0值表示可以进程共享,此时信号量指针必须是进程可访问的,可以通过共享内存来实现
使用:
sem_wait(sem);当信号量为0时阻塞信号量,信号量-1
sem_trywait(sem);当信号为0时不阻塞,而是返回-1,同时errno置为EAGAIN
sem_timewait(sem,struct timespec*);等待超时后如果仍然为0,返回-1,同时errno置为ETIMEOUT
sem_post(sem);释放信号量,信号量+1
sem_close(sem);关闭信号量
sem_unlink(sem);撤销有名信号量;

方法3:文件锁

文件锁用于对一个文件的某个或整个区域的读写进行控制,以保证多个进程对文件的读写是同步的,比如在读之前保证写操作已经完成。
文件锁是通过fcntl函数以及相关的flock数据结构来完成。
fcntl(文件描述符,命令,struct flock)
成功返回依赖于cmd,失败返回-1

struct flock
l_type:锁类型,F_RDLCK,F_WRLCK,F_UNLCK
l_start:文件加锁的起始位置
l_whence:偏移位置:SEEK_SET,SEEK_CUR,SEEK_END
l_len:加锁的长度,如果是0表示整个文件

关于文件锁有如下规定:
1.只对于不同进程,对于同一进程后面加的锁将替换前面加的锁
2.对于不同进程,
如果原来对文件没有锁可以加读锁或写锁;
如果原来有写锁,则阻塞后面的读锁和写锁
如果原来有读锁,则允许后面加读锁,但是阻塞写锁。
3.如果进程退出,则该进程持有的所有锁将释放。
4.如果进程释放文件描述符,则该描述符上的所有锁将释放。
5.通过fork出来的子进程不继承父进程的锁。子进程需要通过fcntl获得锁。
6.通过exec出来的进程继承父进程的锁。
7.系统会自动分裂和合并锁。比如1-32字节加上了锁如果释放了第10字节,则系统会分裂出1-9字节和11-32字节的两把锁,同样如果对于10字节加上了锁,则系统会自动将1-9字节的锁和11-32字节的锁合并成一个锁。

总结:

互斥量,信号量,文件锁的比较
互斥量最快,信号量最慢
若使用互斥量,对于不同进程需要将互斥量映射到都能访问到的内存。
若使用文件锁,可以创建一个空文件,对0字节加写锁达到加锁的目的,对0字节解锁达到解锁的目的。

代码测试

Makefile

target=test
srcs=$(wildcard *.c) 
objs=$(srcs:%.c=%.o)
CC?=gcc
CXX?=g++
cppflags=-c -g -Wall   
ldflags=-g -lpthread -lstdc++ 
%.o:%.c
    $(CC) $< -o $@ $(cppflags)
%.o:%.cpp
    $(CXX) $< -o $@ $(cppflags)
$(target):$(objs)
    $(CXX) $(objs) -o $(target) $(ldflags)
all:$(target)
clean:
    rm -f $(objs) $(target)

main.c

/*************************************************************************
    > File Name: main.c
    > Author: hanhj
    > Mail: hanhj@zx-jy.com 
    > Created Time: 2017年03月09日 星期四 21时34分32秒
 ************************************************************************/
#include<stdio.h>
int test_syn(int argc,char*argv[]);
int main(int argc,char*argv[]){
    test_syn(argc,argv);
    return 0;
}

myfilter.c

/*************************************************************************
    > File Name: myfilter.c
    > Author: hanhj
    > Mail: hanhj@zx-jy.com 
    > Created Time: 2017年03月12日 星期日 09时00分46秒
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>
int main(){
    int c;
    while((c=getchar())!=EOF){
        if(islower(c))
            c=toupper(c);
        putchar(c);
        if(c=='\n')
            fflush(stdout);
    }
    exit(0);
}

test_syn.c

下面的几个函数是我测试的

/***************************************************************

File Name: main.c
Author: hanhj
Mail: hanhj@zx-jy.com
Created Time: 2017年03月10日 星期五 10时20分56秒
**************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/mman.h>
#define POSIX_SEM
//#define XSI_SEM
pthread_mutex_t* g_mutex;
//测试多进程之间的同步
int test_noname_mutex();
int test_named_mutex();
int test_sem(int argc,char *argv[]);
int test_shm(int argc,char*argv[]);
int test_flock(int argc,char*argv[]);
int test_pipe(int argc,char*argv[]);
int test_syn(int argc,char*argv[]){
    //test_noname_mutex();
    //test_named_mutex(argc,argv);
    test_sem(argc,argv);
    //test_shm(argc,argv);
    //test_flock(argc,argv);
    //test_pipe(argc,argv);
    return 0;
}
/*
 *  进程资源互斥保护:
 *  不同的进程如果需要访问同一资源,需要对资源的访问加上保护,保护的方法有 *互斥量,信号量,文件锁。
 *  ------------------------------------
 *  方法1:通过互斥量pthread_mutex_t保护
 *  互斥量pthread_mutex_t本来用在线程中,有pthread_mutex_init,pthread_mutex_lock,pthread_mutex_unlock等函数构成资源保护。如果需要进程资源保护,需要使用pthread_mutexattr_t变量来设置互斥量的属性。
 *  ret=pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
 *  同时,需要使得不同进程能够访问这个互斥量,本例子通过mmap函数来实现:
 *  g_mutex=(pthread_mutex_t*)mmap(NULL,sizeof(pthread_mutex_t),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
 *  g_mutex=(pthread_mutex_t*)mmap(NULL,sizeof(pthread_mutex_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
 *  前面一个函数得到一个无名互斥量的地址,后面的函数得到一个有名互斥量地址。前者适用于有相关关系的进程,比如通过fork出来的线程,由于在同一个程序中,所以可以采用匿名映射。后者,适用于没有相关关系的进程,通过一个公认的文件获得一个映射地址,这样不同的进程可以取同样的映射内存
 * */
void dump(char *s,int size){
    while(size){
        printf("%02x ",*s);
        size--;
        s++;
    }
}
void init_noname_mutex();
void init_named_mutex();
int test_named_mutex(int argc,char *argv[]){
    int child;
    int count;
    count=0;
    if(argc<3){
        printf("Usage:%s parent(0)/child(1) wait_time\n",argv[0]);
        return -1;
    }
    child=atoi(argv[1]);
    init_named_mutex(child);
    int wait_time;
    wait_time=atoi(argv[2]);
    while(1){
        count++;
        if(count>10)
            break;
        printf("lock %s\n",argv[2]);
    //  msync(g_mutex,sizeof(pthread_mutex_t),MS_SYNC);
        pthread_mutex_lock(g_mutex);
        sleep(wait_time);
        pthread_mutex_unlock(g_mutex);
    }
    wait(NULL);
    munmap(g_mutex,sizeof(pthread_mutex_t));
    return 0;
}
int test_noname_mutex(){
    init_noname_mutex();
    int resource;
    int ret;
    pid_t pid;
    pid=fork();
    if(pid==0){//child
        printf("lock child\n");
        ret=pthread_mutex_lock(g_mutex);
        if(ret!=0)perror("child pthread_mutex_lock");
        sleep(10);
        printf("write child\n");
        resource=1;
        ret=pthread_mutex_unlock(g_mutex);
        printf("resource:%d\n",resource);
        if(ret!=0)
            perror("child pthread_mutex_unlock");
        printf("unlock child\n");
    }else{//parent
        sleep(1);
        printf("lock parent\n");
        ret=pthread_mutex_lock(g_mutex);
        if(ret!=0)
            perror("parent pthread_mutex_lock");
        printf("write parent\n");
        resource=2;
        printf("resource:%d\n",resource);
        ret=pthread_mutex_unlock(g_mutex);
        if(ret!=0){
            perror("parent pthread_mutex_unlock");
        }
        printf("unlock parent\n");
    }
    wait(NULL);
    munmap(g_mutex,sizeof(pthread_mutex_t));
    return 0;
}
void init_noname_mutex(){
    int ret;
    g_mutex=(pthread_mutex_t*)mmap(NULL,sizeof(pthread_mutex_t),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    if(g_mutex==MAP_FAILED){
        perror("mmap");
        exit(1);
    }
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    ret=pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
    if(ret!=0){
        perror("init_mutex pthread_mutexattr_setphared");
        exit(1);
    }
    pthread_mutex_init(g_mutex,&attr);
}
void init_named_mutex(int child){
    int ret;
    int fd;
    if(!child){
        fd=open("mymutex",O_CREAT|O_RDWR,0777);
        lseek(fd,sizeof(pthread_mutex_t)-1,SEEK_SET);
        write(fd,"",1);
        printf("init parent\n");
    }else{
        fd=open("mymutex",O_RDWR,00777);
        printf("init child\n");
    }

    g_mutex=(pthread_mutex_t*)mmap(NULL,sizeof(pthread_mutex_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
    printf("mutex:%p\n",g_mutex);
    if(g_mutex==MAP_FAILED){
        perror("mmap");
        exit(1);
    }
    if(child){
        printf("init ok\n");
        return ;
    }
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    ret=pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
    if(ret!=0){
        perror("init_mutex pthread_mutexattr_setphared");
        exit(1);
    }
    pthread_mutex_init(g_mutex,&attr);
    printf("init ok\n");
}
/*
 *  进程资源互斥保护:
 *  不同的进程如果需要访问同样的资源,需要对资源的访问加上访问保护,保护的方法有 互斥量,*信号量,文件锁。
 *  ------------------------------------
 *  方法2:通过信号量来保护
 *  Linux中信号量的实现有两种: XSI_SEM和 POSIX_SEM
*/

#ifdef XSI_SEM
/*
 *XSI_SEM信号量一般用于进程之间通信,与POSIX_SEM相比 XSI_SEM有以下缺点:
 1.获得状态与初始化分两步,不是原子化;
 2.比较原始,使用比较复杂,但是通用;而 POSIX_SEM则是包装了,使用起来比较方便。
 一般来说,信号量的使用包括以下几个步骤:
 1.获得或创建信号量,信号量初始值为1,表示信号量可用
 2.执行P操作,该操作首先测试信号量是否为1,如果为1,则信号量减一,执行后续操作。如果信号量为0,则阻塞
 3.执行V操作,该操作将信号量+1,释放资源。如果此时有被阻塞的进程,则将获得资源继续进行下一步操作。
XSI_SEM信号量相关函数:
    1. ftok关键字
        key_t ftok(路径名,关键字)
        成功返回0,失败返回-1
        该函数获得一个以路径名和使用权限为关键词的唯一的序号以代表一个信号量的标识(不是信号量本身)
    2. semget获得信号量:
        int semget(关键词,期望获得的信号量数量,信号量属性)
        成功返回信号量ID,失败返回-1
        该函数获得一个根据关键词获得的一组信号量
        关键词:就是通过ftok获得的key
        数量:信号量集中的信号量数量;如果是0表示引用已有的信号量
        信号量属性:
            IPC_CREAT:表示是创建
            IPC_EXCL:如果信号量已经存在,则当与IPC_CREAT联合使用时会返回EEXIST错误。
            权限属性:同chmod
    3. semctl:操纵信号量
        semctl(semid,第几个信号,命令,命令参数)
        成功返回0,失败返回-1
        操作信号量的命令包括:
            SETVAL 设置成员变量中的val
            GETVAL 获得成员变量中的val
            IPC_RMID:删除信号量
            命令参数是一个联合
                union semun{
                    int val;//for getval,setval
                    struct senid_ds*buf;//for ipc_start,ipc_set
                    unsigned short *array;//for getall,setall
                    struct seninfo *__buf;
                };
    4.semop:操作信号量:
        semop(semid,信号量操作参数,操作参数中参数数量)
        成功返回0,失败返回-1
        操作参数是一个struct sembuf 结构
            struc sembuf{
                short sem_op;
                unsigned short sem_num
                short sem_flg;
            }
            sem_op:是操作1表示+1,-1表示减一,如果是负值,当前信号量不能满足要求时,则阻塞。
            sem_num:表示操作的是信号集中的第几个,从0开始。
            sem_flg:操作属性,可以是IPC_NOWAIT,当资源不可用时不阻塞,返回错误;
    5.使用完信号量时,通过semctl调用IPC_RMID来删除该信号量。
 * */
union semun{
    int val;
    struct senid_ds*buf;
    unsigned short *array;
    struct seninfo *__buf;
};
int create_sem(int nums);
int get_sem();
int init_sem(int semid,int which);
int P(int semid);
int V(int semid);
int destory_sem(int semid);
#define PATHSEM "/dev/shm/"
#define PROIID 0666
static int comm_sem(int nums,int flags){
    key_t k=ftok(PATHSEM,'a');
    if(k==-1){
        perror("ftok");
        return -1;
    }
    int semid=semget(k,nums,flags);
    if(semid<0){
        perror("semget");
        return -1;
    }
    return semid;
}
int create_sem(int nums){
    return comm_sem(nums,IPC_CREAT|IPC_EXCL|PROIID);
}
int get_sem(){
    return comm_sem(0,0);
}
int init_sem(int semid,int which){
    union semun __semun;
    __semun.val=1;
    if(semctl(semid,which,SETVAL,__semun)<0){
        perror("semctl");
        return -1;
    }
    return 0;
}
int Semop(int semid,int op,int which){
    struct sembuf buf[1];
    buf[0].sem_op=op;
    buf[0].sem_num=which;
//  buf[0].sem_flg=IPC_NOWAIT;
    if(semop(semid,buf,1)==-1){
        perror("semop");
        return -1;
    }
    return 0;
}
int P(int semid){
    return Semop(semid,-1,0);
}
int V(int semid){
    return Semop(semid,1,0);
}
int destory_sem(int semid){
    if(semctl(semid,0,IPC_RMID,NULL)==-1){
        perror("semctl");
        return -1;
    }
    return 0;
}
int test_sem(int argc,char*argv[]){
    int wait;
    int count;
    count=0;
    wait=atoi(argv[1]);
    int semid;
    semid=get_sem();
    if(semid==-1){
        semid=create_sem(1);
        init_sem(semid,0);
    }
    if(semid==-1)
        return -1;
    while(1){
        count++;
        if(count>10)
            break;
        printf("sem:%d wait %d\n",semid,wait);
        P(semid);
        sleep(wait);
        V(semid);
    }
    destory_sem(semid); 
    return 0;
}
#endif
#ifdef POSIX_SEM
/*
 *POSIX_SEM是另外一种信号量,与XSI_SEM相比,更容易使用。
  POSIX_SEM信号量有有名和无名两种,前者可以在多个进程中共享,后者只能在单个进程中共享。
  实际上有名 POSIX_SEM信号量是在/dev/shm目录下产生一个共享内存。
  有名信号量创建:
        sem_t *sem_open(信号名,创建属性,权限属性,初始值)
            信号名:是一个字符串,最好在前面加一个/
            创建属性:与open函数类似,
                O_CREAT:表示是创建,如果不与O_EXCL联合使用,则当该信号量不存在时就创建,否则忽略第3,4个参数返回该信号量。
                O_RDWR:读写
                O_EXCL:独占,如果与 O_CREAT联合使用,则如果该信号量已经存在,调用sem open函数会返回错误。
            权限属性:包括用户,组,其他用户的权限,同chmod
            初始值:如果是二进制信号量1表示该信号量可用,0表示不可用。或者其他值当作计数器使用。
        返回值:成功返回一个指向创建的信号量指针。
                失败:SEM_FAILED
    无名信号量创建:
        int sem_init(信号量指针,是否进程共享,初始值)
            是否共享:非0值表示可以进程共享,此时信号量指针必须是进程可访问的,可以通过共享内存来实现
    使用:
        sem_wait(sem);当信号量为0时阻塞信号量,信号量-1
        sem_trywait(sem);当信号为0时不阻塞,而是返回-1,同时errno置为EAGAIN
        sem_timewait(sem,struct timespec*);等待超时后如果仍然为0,返回-1,同时errno置为ETIMEOUT
        sem_post(sem);释放信号量,信号量+1
        sem_close(sem);关闭信号量
        sem_unlink(sem);撤销有名信号量;
 * */
#include <semaphore.h>
#include<sys/shm.h>
#define PATHSEM "/dev/shm"
#define SEMKEY 'b'
int local_shm_for_sem;
//type 0 nonamed,1 named
sem_t* init_sem(int type,int child){
    int ret;
    sem_t* psem;
    if(!type){
        key_t k=ftok(PATHSEM,SEMKEY);
        if(k==-1)
            perror("ftok");
        int shmid;
        if(!child)
            shmid=shmget(k,sizeof(sem_t),IPC_CREAT|0600);
        else
            shmid=shmget(k,0,0);
        if(shmid==-1){
            perror("shmget");
            return NULL;
        }
        printf("shmid:%d\n",shmid);
        local_shm_for_sem=shmid;
        psem=shmat(shmid,0,0);
        if(psem==(void *)-1){
            perror("shmat");
            return NULL;
        }
        if(!child){
        ret=sem_init(psem,1,1);//shared ,init value=1
        if(ret==-1)
            return NULL;
        }
        return psem;

    }else{
        psem=sem_open("/mysem",O_CREAT|O_RDWR,0777,1);
        if(psem==SEM_FAILED){
            perror("sem_open");
            return NULL;
        }
        printf("open sem:%p\n",psem);
        return psem;
    }
}
int release_shm(int shmid);

int init_named_sem(sem_t* psem){
    psem=sem_open("/mysem",O_CREAT|O_RDWR,0777,1);
    if(psem==SEM_FAILED){
        perror("sem_open");
        return -1;
    }
    printf("open sem:%p\n",psem);
    return 0;
}
int lock_named_sem(sem_t *psem){
    return sem_wait(psem);
}
int unlock_named_sem(sem_t *psem){
    return sem_post(psem);
}
int destory_named_sem(sem_t *psem){
    sem_close(psem);
    sem_unlink("/mysem");
    return 0;
}

int test_sem(int argc,char*argv[]){
    int wait;
    sem_t *psem;
    int count=0;
    int type;
    if(argc<3){
        printf("Usage:%s server/client(0/1) nonamed/named(0/1)  waittime \n",argv[0]);
        return -1;
    }
    int child;
    child=atoi(argv[1]);
    type=atoi(argv[2]);
    psem=init_sem(type,child);
    if(psem!=NULL){
        wait=atoi(argv[3]);
        while(1){
            count++;
            if(count>10)
                break;
            printf("wait %d\n",wait);
            sem_wait(psem);
            sleep(wait);
            sem_post(psem);
            usleep(100);
        }
        if(type==1){//named 
            sem_close(psem);
            sem_unlink("/mysem");
        }else{//noname
            release_shm(local_shm_for_sem);
            sem_destroy(psem);
        }
    }
    return 0;
}
#endif
/*
 *  进程资源互斥保护:
 *  不同的进程如果需要访问同样的资源,需要对资源的访问加上访问保护,保护的方法有 互斥量,信号量,*文件锁。
 *  ------------------------------------
 *  方法3:文件锁
 *  文件锁用于对一个文件的某个或整个区域的读写进行控制,以保证多个进程对文件的读写是同步的,比如在读之前保证写操作已经完成。
 *  文件锁是通过fcntl函数以及相关的flock数据结构来完成。
 *  fcntl(文件描述符,命令,struct flock)
 *  成功返回依赖于cmd,失败返回-1
 *  struct flock
 *  l_type:锁类型,F_RDLCK,F_WRLCK,F_UNLCK
 *  l_start:文件加锁的起始位置
 *  l_whence:偏移位置:SEEK_SET,SEEK_CUR,SEEK_END
 *  l_len:加锁的长度,如果是0表示整个文件
 *  关于文件锁有如下规定:
 *  1.只对于不同进程,对于同一进程后面加的锁将替换前面加的锁
 *  2.对于不同进程,
 *      如果原来对文件没有锁可以加读锁或写锁;
 *      如果原来有写锁,则阻塞后面的读锁和写锁
 *      如果原来有读锁,则允许后面加读锁,但是阻塞写锁。
 *  3.如果进程退出,则该进程持有的所有锁将释放。
 *  4.如果进程释放文件描述符,则该描述符上的所有锁将释放。
 *  5.通过fork出来的子进程不继承父进程的锁。子进程需要通过fcntl获得锁。
 *  6.通过exec出来的进程继承父进程的锁。
 *  7.系统会自动分裂和合并锁。比如1-32字节加上了锁如果释放了第10字节,则系统会分裂出1-9字节和11-32字节的两把锁,同样如果对于10字节加上了锁,则系统会自动将1-9字节的锁和11-32字节的锁合并成一个锁。
*/
//用于简化设置fcntl函数
int lock_reg(int fd,int cmd,int type,off_t offset,int whence,off_t len){
    struct flock lock;
    lock.l_type=type;
    lock.l_start=offset;
    lock.l_whence=whence;
    lock.l_len=len;
    return fcntl(fd,cmd,&lock);
}
//无阻塞的lock
#define read_lock(fd,offset,whence,len) lock_reg(fd,F_SETLK,F_RDLCK,offset,whence,len)
#define write_lock(fd,offset,whence,len) lock_reg(fd,F_SETLK,F_WRLCK,offset,whence,len)
//带阻塞的lock
#define read_lockw(fd,offset,whence,len) lock_reg(fd,F_SETLKW,F_RDLCK,offset,whence,len)
#define write_lockw(fd,offset,whence,len) lock_reg(fd,F_SETLKW,F_WRLCK,offset,whence,len)
//释放锁
#define un_lock(fd,offset,whence,len) lock_reg(fd,F_SETLK,F_UNLCK,offset,whence,len)

pid_t lock_test(int fd,int type,off_t offset,int whence,off_t len){
    struct flock lock;
    lock.l_type=type;
    lock.l_start=offset;
    lock.l_whence=whence;
    lock.l_len=len;
    int ret;
    ret=fcntl(fd,F_GETLK,&lock);
    if(ret<0){
        perror("fcntl");
        return -1;
    }
    if(lock.l_type==F_UNLCK)
        return 0;
    return lock.l_pid;
}
#define is_readlockable(fd,offset,whence,len) (lock_test(fd,F_RDLCK,offset,whence,len)==0)
#define is_writelockable(fd,offset,whence,len) (lock_test(fd,F_WRLCK,offset,whence,len)==0)
int test_flock(int argc,char*argv[]){
    int fd;
    if(argc<3){
        printf("usage %s server/client(0/1) wait\n",argv[0]);
        return -1;
    }
    fd=open("test_file",O_CREAT|O_RDWR,0777);
    if(fd==-1){
        perror("open");
        return -1;
    }
    int child;
    child=atoi(argv[1]);
    int wait;
    int ret=-1;
    wait=atoi(argv[2]);
    int count=0;
    while(1){
        if(!child){
            printf("wait\n");
            #if 0
            //poll method
            if(is_writelockable(fd,0,0,0)){
                printf("can lock ");
            }else continue;
            #endif
            #if 0
            //use write_lock,if is locked,it will
            //retrun immediately with EAGAIN
            ret=write_lock(fd,0,0,0);
            if(ret<0){
                perror("fcntl");
                continue;
            }
            #endif
            ret=write_lockw(fd,0,0,0);//lock whole File
            printf("write :%d ret:%d\n",count,ret);
            write(fd,&count,sizeof(count));
            sleep(wait);
            un_lock(fd,0,0,0);
            usleep(10);
        }else{
            read_lockw(fd,0,0,0);
            int tmp;
            read(fd,&tmp,sizeof(tmp));
            printf("read :%d \n",tmp);
            sleep(wait);
            un_lock(fd,0,0,0);
            usleep(10);
        }

        count++;
        if(count>10)
            break;
    }
    close(fd);
    return 0;
}
/*  
 *  总结:
 *  互斥量,信号量,文件锁的比较
 *  互斥量最快,信号量最慢
 *  若使用互斥量,对于不同进程需要将互斥量映射到都能访问到的内存。
 *  若使用文件锁,可以创建一个空文件,对0字节加写锁达到加锁的目的,对0字节解锁达到解锁的目的。
 *
 *
 * */


/*
 *  
 *  进程间通讯的方法包括 *共享内存,管道,FIFO,消息队列
 *  ------------------------------------
 *  方法1:共享内存
 *  使用共享内存步骤:
 *  1.ftok 获得共享内存标识的关键字
 *  2.shmget 获得共享内存的标识
 *      shmget(key,长度,标志)
 *      成功:返回标识,失败返回-1
 *      标志:
 *          IPC_CREAT
 *          IPC_EXCL
 *          以及权限属性
 *      ps:类似于 XSI_SEM中的semget
 *  3.shmat:获得共享内存地址
 *      shmat(shmid,连接到指定的地址,标志)
 *      地址:如果是0,系统自动给第一个匹配的地址,否则将分配的地址连接到指定的地址
 *      标志:SHM_READONLY 表示只读,否则可读写
 *  4.使用获得的地址
 *  5.shmctl 控制
 *      shmctl(shmid,int cmd,struct shmid_ds* buf)
 *      cmd:控制命令
 *          IPC_STAT:统计信息到buf中
 *          IPC_SET:设置buf信息
 *          IPC_RMID:删除共享内存,如果引用者计数没有到0,则不会删除共享内存,否则会。在本进程中,会导致shmid指向的共享内存失效(仅仅是本进程,对于其他进程不会影响)。
 *  
 *  Linux中程序的内存分配
 *  高端地址
 *  |栈                                              |               
 *  |                                               |
 *  |                                               |
 *  |共享区,介于栈和堆之间                         |
 *  |                                               |
 *  |                                               |
 *  |堆:比如malloc分配的内存                       |
 *  |                                               |
 *  |                                               |
 *  |bss:未初始化数据,比如定义的没有初值的数组    |
 *  |                                               |
 *  |已经初始化的数据                               |
 *  |                                               |
 *  |代码段                                         |
 *  低端地址
 *
 *
 *
 * */
/*
 *  本程序演示如何使用共享内存。
 *  需要注意的是由于两个进程可以同时修改共享内存,所以在修改的地方
 *  加上了使用前面的 POSIX_SEM 信号量保护
 *
 * */
#include<string.h>
typedef struct tag_people{
    char name[100];
    int age;
}People;
People *pPeople;

#define SHMPATH "/dev/shm"
#define SHMKEY 'a'
#define SHMPROI 0666
int init_shm(int type){
    key_t k;
    int ret;
    k=ftok(SHMPATH,SHMKEY);
    if(k==-1){
        perror("ftok");
        return -1;
    }
    if(type==0)//server
        ret=shmget(k,sizeof(People),IPC_CREAT|SHMPROI);
    else
        ret=shmget(k,0,0);
    if(ret==-1){
        perror("shmget");
        return -1;
    }
    int shmid;
    shmid=ret;
    pPeople=shmat(shmid,0,0);
    if(pPeople==(void *)-1){
        perror("shmat");
        return -1;
    }
    return shmid;
}
int release_shm(int shmid){
    //shmdt(pPeople);
    struct shmid_ds buf;
    if(shmctl(shmid,IPC_STAT,&buf)<0){
        perror("shmctl");
        return -1;
    }
    if(buf.shm_nattch==0){
        shmctl(shmid,IPC_RMID,0);
    }
    return 0;

}
int test_shm(int argc,char*argv[]){
    int age;
    int type;
    int shmid;
    int ret;
    if(argc<3){
        printf("Usage:%s server/client name age\n",argv[0]);
        return -1;
    }
    type=atoi(argv[1]);
    ret=init_shm(type);
    shmid=ret;
    if(ret==-1)
        return -1;
    sem_t sem;
    init_named_sem(&sem);
    lock_named_sem(&sem);
    strcpy(pPeople->name,argv[2]);
    unlock_named_sem(&sem);
    age=atoi(argv[3]);
    for(int i=0;i<10;i++){
        lock_named_sem(&sem);
        pPeople->age=i+age;
        unlock_named_sem(&sem);
        printf("name:%s\n",pPeople->name);
        printf("age:%d\n",pPeople->age);
        sleep(1);
    }
    release_shm(shmid);
    destory_named_sem(&sem);
    return 0;
}
/*
 *  
 *  进程间通讯的方法包括共享内存,*管道,FIFO,消息队列
 *  ------------------------------------
 *  方法2:管道
 *  管道分有名管道和无名管道。无名管道用于有相关关系的进程,有名管道用于无相关关系的进程之间通信。
 *  无名管道:
 *      建立:
 *          pipe(int p[2])
 *              成功返回0,失败返回-1
 *          pipe2(int p[2],int flags)
 *              flags同open
 *              成功返回0,失败返回 -1
 *      建立成功后p中的两个数p[0]代表输入端口,p[1]代表输出端口,
 *      一般来说作为使用者要关掉其中一个端口,比如父进程关掉输入端口,子进程关掉输出端口,就建立了一个父进程到子进程的管道。
 *      popen:用于建立到某个进程的使用标准输入或输出通道的一条管道
 *          popen(char *cmd,char*type)
 *          cmd被用于执行:sh -c cmd 命令
 *          type :"r","w"
 *      利用pipe或popen是建立一条单向通道,如果想要建立双向(名词为协同进程)则需要建立两条通道。
 *  有名管道:
 *      建立:mkfifo(路径,mode);
 *          mode同chmod 
 *          成功返回0,失败返回-1
 *          在路径上会出现一个类型为fifo的文件。
 *          建立好这个fifo文件后,就可以正常用open,close,write,read函数了。最后可以用unlink函数来删除这个文件。
 *
 *  需要注意的是:管道有一个管道容量的限制PIPE_BUF,如果输入或输出管道的数据超过这个限制,将分包,如果有多个进程连到一个管道,这样就会导致一个包被打乱了,所以最好包长不要超过buf。
 *      当管道产生错误时会产生一个SIGPIPE的错误,用户可以设置信号来捕捉这个信号:signal(SIGPIPE,void *(int))
 *
 */
int test_pipe1();
int test_pipe2(int argc,char*argv[]);
int test_popen();
int test_pipe(int argc,char*argv[]){
    //test_pipe1();
    test_pipe2(argc,argv);
    //test_popen();
    return 0;
}
int test_popen(){
    FILE * fpin;
    char buff[100];
    fpin=popen("myfilter","r");
    if(fpin==NULL){
        perror("popen");
        return -1;
    }
    while(1){
        fputs("prompt>",stdout);
        fflush(stdout);
        if(fgets(buff,100,fpin)!=NULL){
            if(!strncmp(buff,"EXIT",4))
                break;
                //exit(0);
            fputs("trans>",stdout);
            if(fputs(buff,stdout)==EOF)
                perror("fputs");
        }else
            break;

    }
    if(pclose(fpin)==-1)
        perror("pclose");
    fputs("ddd",stdout);
    return 0;
}
int test_pipe1(){
    int fd[2];
    int ret;
    ret=pipe(fd);
    if(ret<0){
        perror("pipe");
        return -1;
    }
    if(fork()==0){//child
        close(fd[1]);
        int c;
        while(1){
            read(fd[0],&c,1);

            putchar(c);
        }
    }else{//parent
        close(fd[0]);
        while(1){
            int c;
            c=getchar();
            write(fd[1],&c,1);
        }
    }
}
/*
 *  有名管道:用于没有关系的进程之间通信
 *
 *  创建:mkfifo(路径,mode)
 *      路径:
 *      mode:权限,同chmod 
 *  当建立一个有名管道之后,就可以像正常文件一样打开和关闭了。
 *  open(文件名,flags,modes)
 *      flags:  O_CREAT,O_RDWR,O_RDONLY,O_WRONLY,O_EXCL,O_TRUNC,O_DIRECT,O_NONBLOCK,O_APPEND
 *      modes:chmod
 *  
 *  unlink(文件名) 删除文件
 *
 * */

#include <sys/stat.h>
static void sigpipe(int err){
    //
    //perror("")
    printf("sigpipe caught %x",err);
    exit(-1);
}
int test_pipe2(int argc,char*argv[]){
    int ret;
    int fd;
    int wait;
    wait=atoi(argv[1]);
    signal(SIGPIPE,sigpipe);
    ret=mkfifo("myfifo",0777);
    if(ret==-1){
        perror("mkfifo");
        return -1;
    }
    if(fork()==0){//child
    sem_t sem;
    init_named_sem(&sem);
        fd=open("myfifo",O_RDWR,0777);
        //fflush(fd2);
        int count=0;
        while(1){
            count++;
            if(count>10)
                break;
            //lock_named_sem(&sem);
            //write(fd,&count,1);
            //unlock_named_sem(&sem);
            int tmp;
            sleep(wait);
            //lock_named_sem(&sem);
            if(read(fd,&tmp,1)!=-1)
            printf("child read:%d\n",tmp);
            //unlock_named_sem(&sem);
        }

    }else{//parent
    sem_t sem;
    init_named_sem(&sem);
        fd=open("myfifo",O_RDWR,0777);
        int count;
        //int tmp;
        int tmp2;
        count=0;
        //usleep(10);
        while(1){
            count++;
            if(count>10)
                break;
            //if(read(fd,&tmp,1)!=-1)
            //printf("\tparent read:%d\n",tmp);
            tmp2=count+100;
            lock_named_sem(&sem);
            write(fd,&tmp2,1);
            unlock_named_sem(&sem);
            sleep(wait);
        }
    }
    close(fd);
    unlink("myfifo");


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值