Unix高级编程:pause函数mysleep的实现、可重入函数、定时器、进程间通讯

一、pause函数的使用
该函数使调用进程进入无时限的睡眠状态
"pause"(2) //暂停/中止
#include <unistd.h>
int pause(void);
功能:等待信号
参数:void
返回值:
成功 - 不返回,阻塞信号,信号捕获后再返回
失败 - 返回-1,errno被设置
/* 使用alarm和pause实现sleep的功能,mysleep.c */
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handle(int signum) {
    printf(" signum is %d\n", signum);
    return ;
}
unsigned int mysleep(unsigned int seconds) {
    alarm(seconds);
    pause();
    return 1;
}
int main(void) {
    signal(SIGALRM, handle); //向进程注册信号
    //alarm(1);
    while(1) {
        mysleep(2);//睡眠间隔时间2秒/次,每2秒打印两句
        printf("hello,world!\n"); // '\n'必须加,否则不打印
        #if 0
        alarm(1);//设置闹钟时间为1秒
        pause();//等待信号到来
        #endif
    }   
    return 0;
}


二、可重入函数
函数中所有变量的空间只能在栈里分配,这样的函数称为"可重入函数"
可以安全的调用自身(从信号处理中或从其他线程中)的函数,为了使函数可重入,函数"绝不能操作静态数据,只访问在栈里分配的数据和调用者提供的数据",同时也不得调用任何不可重入的函数。


参与"信号处理的函数必须是可重入函数"


"信号处理函数"和"正常的执行流程"是不同的两个执行路线。


/*举例验证可重入函数,代码 retest.c*/
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handle(int signum) { //该函数也不能malloc,内存也会共享
    static int count = 0; //去掉staric,count在栈帧一直打印1
    int value;
    value = count;
    count = value + 1; //count++
    usleep(5000); //休眠微秒(秒-毫秒-微妙,1000倍)
    printf("count = %d\n", count);
    return ;
}
int main(void) {
    signal(2, handle);
    while(1) {
        handle(3);
    }   
    return 0;
}


补充:
子进程结束的时候会给父进程发送一个信号 SIGCHLD 。
父进程为子进程收尸的时候接收的就是此信号。


三、setitimer计时器
"setitimer"(2)
#include <sys/time.h>
//int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
             struct itimerval *old_value);
功能:设置一个间歇时间(计时器/定时器)
参数:
"which" 3选1
ITIMER_REAL -> SIGALRM 信号(倒计时,按时间减少真实时间)
ITIMER_VIRTUAL -> SIGVTALRM 信号(仅当遇到该信号时开始倒计时)
ITIMER_PROF -> SIGPROF 信号
"new_value" 结构体变量地址
"old_value" 结构体变量地址
struct itimerval {
   struct timeval it_interval; /*next value重复间隔时间*/
   struct timeval it_value;    /*current value初始间隔时间(倒计时)*/
};
struct timeval {
   long tv_sec;        /*seconds秒*/  /** 秒-毫秒-微妙:各1000倍 **/
   long tv_usec;       /*microseconds微秒*/
};
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
/* 举例使用setitimer实现定时器,每隔1秒发送1次,timer.c */
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
void handle(int signum) {
    printf(" pa...\n");
    usleep(200000);
    printf(" pa...\n");
    usleep(200000);
    printf(" pa...\n");
    return ;
}
int main(void) {
    struct itimerval new;
    signal(SIGALRM, handle);
    //初始化间隔时间的信息
    new.it_value.tv_sec = 3; //初始间隔时间,倒计时3秒
    new.it_value.tv_usec = 0;
    new.it_interval.tv_sec = 2; //重复间隔时间,每2秒
    new.it_interval.tv_usec = 0;
    //设置闹钟
    setitimer(ITIMER_REAL, &new, NULL);
    while(1);
    return 0;
}


信号小结:
1)什么是信号?可靠信号和不可靠信号
2)信号的产生
3)信号捕获
4)信号阻塞和信号未决
5)可重入函数


四、进程间通讯
可以"实现进程间通讯的方式":
管道(无名/有名)/环境变量/信号
"system v IPC" 进程通信包括以下三种: //IPC交互进程通信
1. 消息队列
2. 共享内存
3. 信号量集


为了实现进程之间的数据交换,系统内核会为参与通信的诸方维护一个内核对象(类似一个结构体变量),记录和通信有关的各种配置参数和运行时信息,叫"IPC对象"
系统中的每个IPC对象都有唯一的非负整数形式的标识符,所有与IPC相关的操作,都需要提供"IPC对象标识符",标识符是IPC对象的内部名,为了使多个合作进程能够在同一个IPC对象上会合,需要提供一个外部名方案,为此使用了"键"


"每一个IPC对象都与一个键相关联,1对1关系"


内核提供了一块/** 缓存 **/,用以维护进程间的通讯。
使用"ipcs"命令查看内核提供的这些缓存。
命令行输入"man ipcs" 或 "ipcs -h" 查看所有ipcs的子命令


在内存中为每一块内存都标记了一个id。
要想获取这个id,需要在/** 用户态 **/下有一个唯一的"键值"去对应。
键值:IPC对象唯一的标记


获取键值,使用ftok函数
"ftok"(3)
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:将pathname和proj_id转换为system v IPC键值
参数:
"pathname" 有效的文件名
"proj_id" 取一个数字的低8位(0~255) //取21和取277(277-256)结果都是21
//多次调用ftok的时候,参数pathname和proj_id的组合一样,那么得到的键值也是一样的,必须保证键值唯一。
返回值:
成功 - 返回一个键值
失败 - 返回 -1,errno被设置
/* 举例验证,获取一个有效的键值,ftok.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main(void) {
    key_t key;
    key = ftok(".", 21);//创建一个键值
    if(-1 == key) {
        perror("ftok");
        return 1;
    } else {
        printf("ftok success...\n");
    }
    return 0;
}


"消息队列"
获取消息队列的ID,使用msgget(2)函数
"msgget"(2)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:获取一个消息队列的id
参数:
"key" 跟消息队列向关联的键值,ftok(3)的返回值
"msgflg" 
IPC_CREAT 如果消息队列不存在,则创建并返回创建的消息队列id
如果存在,则返回该消息队列id
IPC_EXCL 如果消息队列不存在,则创建并返回创建的消息队列id
如果消息队列存在,masgget失败,errno被设置为EEXIST
可以指定消息队列的操作权限。做"或 |"操作,权限参见open(2)的mode。
返回值:
成功 - 返回消息队列的id
失败 - 返回 -1,errno被设置
/* 举例验证,根据键值获取消息队列的id,msgget.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(void) {
    key_t key;
    key = ftok(".", 21);//获取键值
    if(-1 == key) {
        perror("ftok");
        return 1;
    }   
    //根据键值获取消息队列
    int msgid = msgget(key, IPC_CREAT|0664);
    if(-1 == msgid) {
        perror("msgget");
        return 2;
    } else {
        printf("msgid = %d\n", msgid);
    }   
    return 0;
}


向消息队列中写数据,使用msgsnd函数
"msgsnd"(2) /** 写入消息 **/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:往消息队列里添加数据
参数:
"msqid" msgget(2)的返回值,就是队列的id
"msgp" 消息的结构体
"msgsz" 消息的长度,不包含消息的类型mtype,只有mtext的长度
"msgflg" 
IPC_NOWAIT 非阻塞,消息队列没有空间的时候,立即返回
0 阻塞,消息队列没有空间的时候,阻塞等待
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


struct msgbuf {
   long mtype;       /*message type, must be > 0 */
   char mtext[1];    /*message data该数组可变长,该字节往后都是数据*/
}; "使用此结构体之前要先声明该结构体(数组变长),并定义结构体变量"


/* 举例验证,将字符串hello,world放入消息队列当中 processA.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
int main(void) {
    key_t key;
    key = ftok(".", 21);//创建键值
    if(-1 == key) {
        perror("ftok");
        return 1;
    }   
    int msqid = msgget(key, IPC_CREAT|0664);//获取消息队列id
    if(-1 == msqid) {
        perror("msgget");
        return 2;
    }   
    struct msgbuf {
        long mtype;
        char str[20]; /** 定义包含可变长数组的结构体 **/
    }msg;
    msg.mtype = 3; //只要大于0即可,读取消息队列数据时此数字需一致
    strcpy(msg.str, "hello,world!");
    //向消息队列里写数据
    msgsnd(msqid, &msg, strlen(msg.str)+1, 0); 
    return 0;
}


"msgrcv"(2)  /** 移除消息 **/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, \
               int msgflg);
功能:从msqid消息队列中移除一条消息
参数:
"msqid" msgget(2)的返回值,消息队列的id
"msgp" 指定一块内存空间,将消息存放到这块空间里
"msgsz" metxt的最大尺寸
"msgtyp" 消息的类型(需要与添加消息的时候指定的数字一致)
"msgflg" 
IPC_NOWAIT 消息队列中没有消息,立即返回
0 消息队列中没有消息,等待
返回值:
成功 - 返回从消息队列中获取的消息的字节数
失败 - 返回 -1,errno被设置 
/* 举例验证,从消息队列中获取消息,将消息输出到屏幕 processB.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
int main(void) {
    key_t key;
    key = ftok(".", 21);//获取键值
    if(-1 == key) {
        perror("ftok");
        return 1;
    }   
    //获取消息队列的id
    int msqid = msgget(key, IPC_CREAT|0664);
    if(-1 == msqid) {
        perror("msgget");
        return 2;
    }   
    struct msgbuf { //定义接收消息的结构体类型和变量msg
        long mtype;
        char str[50];
    }msg;
    //从消息队列里面获取消息
    int bytes = msgrcv(msqid, &msg, sizeof(msg.str), 3, 0); 
    printf("%s\n", msg.str); /** 消息获取后,会从消息队列移除 **/
    return 0;
}


"共享内存"
由内核维护的一块内存,将这块内存映射到不同进程的虚拟地址空间里,在不同的进程里操作这块内存,起到进程间通讯的作用。


"shmget"(2)
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:分配一块共享内存区域
参数:
"key" 根据key的值获取共享内存的id,ftok(3)的返回值
"size" 需要的共享内存的大小/尺寸
"shmflg" 
IPC_CREAT 如果存在共享内存,则返回;不存在就创建
返回值:
成功 - 返回共享内存的id
失败 - 返回 -1
/* 举例创建自己的内核共享内存,获取共享的内存的id  shmget.c*/
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void) {
    key_t key;
    key = ftok(".", 22);//获取键值
    if(-1 == key) {
        perror("ftok");
        return 1;
    }   
    //根据键值获取共享内存的id
    int shmid = shmget(key, 1024, IPC_CREAT|0664);
    if(-1 == shmid) {
        printf("shmget error...\n");
        return 2;
    } else {
        printf("shared memory success...\n");
        printf("shmid = %d\n", shmid);
    }   
    return 0;
}


将"内核"维护的共享内存,"映射"到进程("用户")的虚拟地址空间里:
"shmat"(2) /** 映射 **/
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:将共享内存映射到虚拟地址空间里
参数:
"shmid" 共享内存的id,shmget(2)的返回值
"shmaddr" NULL 代表系统自动分配虚拟空间
"shmflg" 
SHM_RDONLY 只读(共享内存权限为只读)
0 可读可写
返回值:
成功 - 返回跟共享内存相关的虚拟地址(用户空间)
失败 - 返回(void *)-1,errno被设置


"shmdt"(2) /** 解除映射 **/
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:解除shmat的映射
参数:"shmaddr" shmat(2)的返回值
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置


/* 使用共享内存实现两个进程间的通信 shmA.c shmB.c */
/** A **/
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
int main(void) {
    key_t key;
    key = ftok(".", 22);//获取键值
    if(-1 == key) {
        perror("ftok");
        return 1;
    }
    //根据键值获取跟键值相关的共享内存区域
    int shmid = shmget(key, 1024, IPC_CREAT|0664);
    if(-1 == shmid) {
        perror("shmget");
        return 2;
    } else {
        void *p = shmat(shmid, NULL, 0);//将共享内存映射到虚拟地址
        if((void *)-1 == p) {
            perror("shmat");
            return 3;
        } else {
            printf("shmat success...\n");
            printf("p address is %p\n", p);
            strcpy(p, "hello,world!");
            printf("A - p is %s\n", (char *)p);
            //解除映射
            shmdt(p);
        }
    }
    return 0;
}
/** B **/
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
int main(void) {
    key_t key = ftok(".", 22);
    if(-1 == key) {
        perror("ftok");
        return 1;
    }   
    int shmid = shmget(key, 1024, IPC_CREAT|0664);
    if(-1 == shmid) {
        perror("shmget");
        return 2;
    } else {
        void *p = shmat(shmid, NULL, 0); 
        if((void *)-1 == p) {
            perror("shmat");
            return 3;
        } else {
            printf("B - p is %s\n", (char *)p);
            shmdt(p);
        }   
    }   
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜源Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值