17 内存映射(1)
- 内存映射(Memory-mapped I/O)是1将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件
内存映射相关系统调用
/*
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
- 功能:将一个文件或者设备的数据映射到内存中
- 参数:
- void *addr: NULL, 由内核指定
- length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。
获取文件的长度:stat lseek
- prot : 对申请的内存映射区的操作权限
-PROT_EXEC :可执行的权限
-PROT_READ :读权限
-PROT_WRITE :写权限
-PROT_NONE :没有权限
要操作映射内存,必须要有读的权限。
PROT_READ、PROT_READ|PROT_WRITE
- flags :
- MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项
- MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)
- fd: 需要映射的那个文件的文件描述符
- 通过open得到,open的是一个磁盘文件
- 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。
prot: PROT_READ open:只读/读写
prot: PROT_READ | PROT_WRITE open:读写
- offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不便宜。
- 返回值:返回创建的内存的首地址
失败返回MAP_FAILED,(void *) -1
int munmap(void *addr, size_t length);
- 功能:释放内存映射
- 参数:
- addr : 要释放的内存的首地址
- length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。
*/
/*
使用内存映射实现进程间通信:
1.有关系的进程(父子进程)
- 还没有子进程的时候
- 通过唯一的父进程,先创建内存映射区
- 有了内存映射区以后,创建子进程
- 父子进程共享创建的内存映射区
2.没有关系的进程间通信
- 准备一个大小不是0的磁盘文件
- 进程1 通过磁盘文件创建内存映射区
- 得到一个操作这块内存的指针
- 进程2 通过磁盘文件创建内存映射区
- 得到一个操作这块内存的指针
- 使用内存映射区通信
注意:内存映射区通信,是非阻塞。
*/
#include<stdio.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
//1、打开一个文件
int fd = open("text.txt",O_RDWR);
//获取文件的大小
int size = lseek(fd,0,SEEK_END);
//2、创建内存映射区
void *ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(ptr == MAP_FAILED){
perror("mmap");
return -1;
}
//3、创建子进程
pid_t pid = fork();
if(pid > 0){
wait(NULL);
//父进程
char buf[64];
strcpy(buf,(char *)ptr);
printf("read data : %s \n",buf);
}else if(pid == 0){
//子进程
strcpy((char *)ptr,"nihao a, son \n");
}
//关闭内存映射区
munmap(ptr,size);
return 0;
}
使用内存映射实现没有亲缘关系的进程间通信
write.c
#include<stdio.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
//1、打开文件
int fd = open("text.txt",O_RDWR);
if(fd == -1){
perror("open");
return -1;
}
//获取文件长度
int size = lseek(fd,0,SEEK_END);
//内存映射
void *ptr = mmap(NULL,size,PROT_WRITE | PROT_READ,MAP_SHARED,fd,0);
if(ptr == MAP_FAILED){
perror("mmap");
return -1;
}
//写数据
char buf[1024];
int nIndex = 0;
while(1){
sleep(2);
memset(buf,0,1024);
sprintf(buf,"%d hello!",nIndex++);
printf("write: %s \n",buf);
strcpy((char *)ptr,buf);
}
//释放内存映射
munmap(ptr,size);
//关闭文件
close(fd);
return 0;
}
read.c
#include<stdio.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
//打开文件
int fd = open("text.txt",O_RDWR);
if(fd == -1){
perror("open");
return -1;
}
//获取文件长度
int size = lseek(fd,0,SEEK_END);
//建立内存映射
void *ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(ptr == MAP_FAILED){
perror("mmap");
return -1;
}
//读数据
char buf[1024];
while(1){
sleep(2);
memset(buf,0,1024);
strcpy(buf,(char *)ptr);
printf("read : %s \n",buf);
}
//释放内存映射
munmap(ptr,size);
//关闭文件
close(fd);
return 0;
}
18 内存映射(2)
1.如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
void * ptr = mmap(...);
ptr++; 可以对其进行++操作
munmap(ptr, len); // 错误,要保存地址
2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
错误,返回MAP_FAILED
open()函数中的权限建议和prot参数的权限保持一致。
3.如果文件偏移量为1000会怎样?
偏移量必须是4K的整数倍,返回MAP_FAILED
4.mmap什么情况下会调用失败?
- 第二个参数:length = 0
- 第三个参数:prot
- 只指定了写权限
- prot PROT_READ | PROT_WRITE
第5个参数fd 通过open函数时指定的 O_RDONLY / O_WRONLY
5.可以open的时候O_CREAT一个新文件来创建映射区吗?
- 可以的,但是创建的文件的大小如果为0的话,肯定不行
- 可以对新的文件进行扩展
- lseek()
- truncate()
6.mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open("XXX");
mmap(,,,,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭,没有任何影响。
7.对ptr越界操作会怎样?
void * ptr = mmap(NULL, 100,,,,,);
4K
越界操作操作的是非法的内存 -> 段错误
内存映射实现文件复制的功能
// 使用内存映射实现文件拷贝的功能
/*
思路:
1.对原始的文件进行内存映射
2.创建一个新文件(拓展该文件)
3.把新文件的数据映射到内存中
4.通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中
5.释放资源
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(){
//1.对原始的文件进行内存映射
int fd = open("english.txt",O_RDWR);
if(fd == -1){
perror("open");
return -1;
}
//获取原始文件的大小
int len = lseek(fd,0,SEEK_END);
//2.创建一个新文件(拓展该文件)
int fd1 = open("cpy.txt",O_RDWR | O_CREAT,0664);
if(fd1 == -1){
perror("open");
return -1;
}
//对新创建的文件进行扩展(可以使用lseek和truncate)
truncate("cpy.txt",len);
write(fd1," ",1);
//3.分别做内存映射
void *ptr = mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd,0);
void *ptr1 = mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd1,0);
if(ptr == MAP_FAILED){
perror("mmap");
return -1;
}
if(ptr1 == MAP_FAILED){
perror("mmap");
return -1;
}
//内存拷贝
memcpy(ptr1,ptr,len);
//释放资源
munmap(ptr1,len);
munmap(ptr,len);
close(fd1);
close(fd);
return 0;
}
匿名映射
/*
匿名映射:不需要文件实体进程一个内存映射
只能进行父子间映射,没有亲情关系的进程不可以,因为是匿名
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
//1、创建匿名内存映射区
int len = 4096;
void *ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);
if(ptr == MAP_FAILED){
perror("mmap");
return -1;
}
//父子进程间通信
pid_t pid = fork();
if(pid > 0){
//父进程
strcpy((char *)ptr,"hello world");
wait(NULL);
}else if(pid == 0){
//子进程
sleep(1);
printf("%s\n",(char *)ptr);
}
//释放内存映射区
munmap(ptr,len);
return 0;
}
19 信号概述
信号的概念
信号的5种默认处理动作
20 kill、raise、abort函数
信号相关的函数
/**
* #include<sys/types.h>
* #include<signal.h>
* int kill(pid_t pid, int sig);
* 功能:给某个进程或者进程组pid,发送某个信号sig
* 参数:
* pid:需要发送给的进程的id
* >0:将信号发送给指定的进程
* =0:将信号发送给当前的进程组
* =-1:将信号发送给每一个有权限接收这个信号的进程
* <-1:这个pid=某个进程组的id取反(-1234)
* sig:需要发送的信号的编号或者宏值,0表示不发送任何信号
* kill(getppid(),sig);
* kill(getpid(),sig);
*/
/*
int raise(int sig);
功能:给当前进程发送信号
参数:
sig:要发送的信号
返回值:
成功:0
失败:非0
kill(getpid(),sig);
void abort(void);
功能:发送SIGABRT信号给当前的进程,杀死当前进程
如果用kill实现就是kill(getpid(),SIGABRT);
*/
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main(){
pid_t pid = fork();
if(pid == 0){
for(int i=0;i<6;++i){
printf("child process !!!\n");
sleep(1);
}
}else if(pid >0){
printf("parent process \n");
sleep(2);
printf("kill child\n");
kill(pid,SIGINT);
}
return 0;
}
21 alarm函数
/*
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
函数会给当前的进程发送一个信号:SIGALARM
参数:
seconds:倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)
取消一个定时器,通过alarm(0)
返回值:
之前没有定时器,返回0
之前有定时器,返回之前定时器剩余的时间
SIGALARM:默认终止当前的进程,每一个进程都有且只有唯一的一个定时器
alarm(10); ->返回0
过了1秒
alarm(5); ->返回9
alarm(100); -> 该函数是不阻塞的
*/
#include<stdio.h>
#include<unistd.h>
int main(){
int seconds = alarm(5);
printf("seconds = %d \n",seconds);//0
sleep(2);
seconds = alarm(2);
printf("seconds = %d \n",seconds);//3
while(1){}
return 0;
}
案例:计算机1秒能数多少个数
#include<stdio.h>
#include<unistd.h>
int main(){
int seconds = alarm(1);
int count=0;
while(1){
printf("数字:%d\n",count++);
}
return 0;
}
22 setitimer定时器函数
/*
#include<sys/time.h>
int setitimer(int which,const struct itimerval *new_value,
struct itimerval *old_value);
功能:设置定时器(闹钟),可以替代alarm函数,精度:微秒us,
可以实现周期性定时
参数:
which:定时器以什么时间定时
ITIMER_REAL:真实时间,时间到达,发送SIGALRM 常用
ITIMER_VIRTUAL:用户时间,时间到达,发送SIGVTALRM
ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,
时间到达,发送SIGPROF
new_value:设置定时器的属性
struct itimerval{ //定时器的结构体
struct timeval it_interval; //每个阶段的时间,间隔时间
struct timeval it_value; //延迟多长时间执行定时器
};
struct timeval { //时间的结构体
time_t tv_sec; //秒数
suseconds_t tv_usec; //微秒
};
过10秒后,每个2秒定时一次
old_value:记录上一次的定时的时间参数,一般不使用,指定NULL
返回值:成功0,失败-1,并返回错误号
*/
/*
#include<sys/time.h>
int setitimer(int which,const struct itimerval *new_value,
struct itimerval *old_value);
功能:设置定时器(闹钟),可以替代alarm函数,精度:微秒us,
可以实现周期性定时
参数:
which:定时器以什么时间定时
ITIMER_REAL:真实时间,时间到达,发送SIGALRM 常用
ITIMER_VIRTUAL:用户时间,时间到达,发送SIGVTALRM
ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间来计算,
时间到达,发送SIGPROF
new_value:设置定时器的属性
struct itimerval{ //定时器的结构体
struct timeval it_interval; //每个阶段的时间,间隔时间
struct timeval it_value; //延迟多长时间执行定时器
};
struct timeval { //时间的结构体
time_t tv_sec; //秒数
suseconds_t tv_usec; //微秒
};
过10秒后,每个2秒定时一次
old_value:记录上一次的定时的时间参数,一般不使用,指定NULL
返回值:成功0,失败-1,并返回错误号
*/
#include<stdio.h>
#include<sys/time.h>
#include<stdlib.h>
//过3秒以后,每隔2秒定时一次
int main(){
struct itimerval new_value;
//设置间隔的时间,秒和微秒
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟的时间
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
printf("定时器开始了。。。。\n");
if(ret == -1){
perror("setitimer");
return -1;
}
getchar();
return 0;
}
案例还未实现,需要下一节的信号捕捉
23 signal信号捕捉函数
信号捕捉函数
/*
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- 功能:设置某个信号的捕捉行为
- 参数:
- signum: 要捕捉的信号
- handler: 捕捉到信号要如何处理
- SIG_IGN : 忽略信号
- SIG_DFL : 使用信号默认的行为
- 回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
回调函数:
- 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
- 不是程序员调用,而是当信号产生,由内核调用
- 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。
- 返回值:
成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
失败,返回SIG_ERR,设置错误号
SIGKILL SIGSTOP不能被捕捉,不能被忽略。
*/
#include<stdio.h>
#include<sys/time.h>
#include<stdlib.h>
#include<signal.h>
void myalarm(int num){
printf("捕捉到了信号的编号是:%d\n",num);
printf("xxxxxxxxx\n");
}
//过3秒以后,每隔2秒定时一次
int main(){
//注册信号捕捉
//signal(SIGALRM,SIG_IGN);
//signal(SIGALRM,SIG_DFL);
// sighandler_t ret = signal(SIGALRM,myalarm);
// if(ret == SIG_ERR){
// perror("signal");
// return -1;
// }
signal(SIGALRM,myalarm);
struct itimerval new_value;
//设置间隔的时间,秒和微秒
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟的时间
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
printf("定时器开始了。。。。\n");
if(ret == -1){
perror("setitimer");
return -1;
}
getchar();
return 0;
}
24 信号集及相关函数
信号集
阻塞信号集和未决信号集
1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建)
2.信号产生但是没有被处理 (未决)
- 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
- SIGINT信号状态被存储在第二个标志位上
- 这个标志位的值为0, 说明信号不是未决状态
- 这个标志位的值为1, 说明信号处于未决状态
3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
- 阻塞信号集默认不阻塞任何的信号
- 如果想要阻塞某些信号需要用户调用系统的API
4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
- 如果没有阻塞,这个信号就被处理
- 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
信号集相关的函数
/*
以下信号集相关的函数都是对自定义的信号集进行操作。
int sigemptyset(sigset_t *set);
- 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
- 参数:set,传出参数,需要操作的信号集
- 返回值:成功返回0, 失败返回-1
int sigfillset(sigset_t *set);
- 功能:将信号集中的所有的标志位置为1
- 参数:set,传出参数,需要操作的信号集
- 返回值:成功返回0, 失败返回-1
int sigaddset(sigset_t *set, int signum);
- 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
- 参数:
- set:传出参数,需要操作的信号集
- signum:需要设置阻塞的那个信号
- 返回值:成功返回0, 失败返回-1
int sigdelset(sigset_t *set, int signum);
- 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
- 参数:
- set:传出参数,需要操作的信号集
- signum:需要设置不阻塞的那个信号
- 返回值:成功返回0, 失败返回-1
int sigismember(const sigset_t *set, int signum);
- 功能:判断某个信号是否阻塞
- 参数:
- set:需要操作的信号集
- signum:需要判断的那个信号
- 返回值:
1 : signum被阻塞
0 : signum不阻塞
-1 : 失败
*/
#include <signal.h>
#include <stdio.h>
int main(){
// 创建一个信号集
sigset_t set;
// 清空信号集的内容
sigemptyset(&set);
// 判断SIGINT是否在信号集set里
int ret = sigismember(&set,SIGINT);
if(ret == 0){
printf("sigint 未阻塞 \n");
}else if(ret == 1){
printf("sigint 阻塞 \n");
}
// 添加几个信号到信号集中SIGINT,SIGQUIT
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
// 判断SIGINT是否在信号集中
ret = sigismember(&set,SIGINT);
if(ret == 0){
printf("sigint 未阻塞 \n");
}else if(ret == 1){
printf("sigint 阻塞 \n");
}
// 从信号集中删除一个信号SIGQUIT
ret = sigdelset(&set,SIGQUIT);
if(ret == 0){
printf("sigquit 删除成功");
}else if(ret == -1){
printf("sigquit 删除失败");
}
// 判断SIGQUIT是否还在信号集中
ret = sigismember(&set,SIGQUIT);
if(ret ==1){
printf("sigquit 阻塞 \n");
}else if(ret == 0){
printf("sigquit 未阻塞 \n");
}
return 0;
}
25 sigprocmask函数使用
注:执行程序时在后面加一个& 就表示在后台执行程序,该程序输出过程中输入命令(例如ls)是有效的,如果在前台执行,输入命令无效。
/*
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
- 参数:
- how : 如何对内核阻塞信号集进行处理
SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变
假设内核中默认的阻塞信号集是mask, mask | set
SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞
mask &= ~set
SIG_SETMASK:覆盖内核中原来的值
- set :已经初始化好的用户自定义的信号集
- oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL
- 返回值:
成功:0
失败:-1
设置错误号:EFAULT、EINVAL
int sigpending(sigset_t *set);
- 功能:获取内核中的未决信号集
- 参数:set,传出参数,保存的是内核中的未决信号集中的信息。
*/
// 编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕
// 设置某些信号是阻塞的,通过键盘产生这些信号
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
// 设置2、3号信号阻塞
sigset_t set;
sigemptyset(&set);
// 将2号3号信号添加到信号集中
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//修改内核中的阻塞信号集
sigprocmask(SIG_BLOCK,&set,NULL);
int num =0;
while(1){
num++;
// 获取当前的未决信号集的数据
sigset_t pendingset;
sigemptyset(&pendingset);
sigpending(&pendingset);
//遍历前32位
for(int i=1;i<=31;++i){
if( sigismember(&pendingset,i)==1){
printf("1 ");
}else if(sigismember(&pendingset,i)==0){
printf("0 ");
}else{
perror("sigismember");
return -1;
}
}
printf("\n");
sleep(2);
if(num == 10){
//解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
}
}
return 0;
}
26 sigaction信号捕捉函数
信号捕捉函数
/*
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
- 功能:检查或者改变信号的处理。信号捕捉
- 参数:
- signum : 需要捕捉的信号的编号或者宏值(信号的名称)
- act :捕捉到信号之后的处理动作
- oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL
- 返回值:
成功 0
失败 -1
struct sigaction {
// 函数指针,指向的函数就是信号捕捉到之后的处理函数
void (*sa_handler)(int);
// 不常用
void (*sa_sigaction)(int, siginfo_t *, void *);
// 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
sigset_t sa_mask;
// 使用哪一个信号处理对捕捉到的信号进行处理
// 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
int sa_flags;
// 被废弃掉了
void (*sa_restorer)(void);
};
*/
#include<stdio.h>
#include<sys/time.h>
#include<stdlib.h>
#include<signal.h>
void myalarm(int num){
printf("捕捉到了信号的编号是:%d\n",num);
printf("xxxxxxxxx\n");
}
//过3秒以后,每隔2秒定时一次
int main(){
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myalarm;
sigemptyset(&act.sa_mask); //清空临时阻塞信号集
//注册信号捕捉
sigaction( SIGALRM,&act,NULL);
struct itimerval new_value;
//设置间隔的时间,秒和微秒
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
//设置延迟的时间
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
printf("定时器开始了。。。。\n");
if(ret == -1){
perror("setitimer");
return -1;
}
//getchar();
while(1){}
return 0;
}
27 SIGCHLD信号
查看信号的命令 kill -l
/*
SIGCHLD信号产生的3个条件:
1.子进程结束
2.子进程暂停了
3.子进程继续运行
都会给父进程发送该信号,父进程默认忽略该信号。
使用SIGCHLD信号解决僵尸进程的问题。
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h>
#include<sys/wait.h>
void myFun(int num){
printf("捕捉到的信号: %d\n",num);
//捕捉到信号说明有子进程结束了,
//结束之后要回收子进程PCB的资源
//wait(NULL);
while(1){
int ret = waitpid(-1,NULL,WNOHANG);
if(ret > 0){
printf("child die, pid = %d\n",ret);
}else if(ret == 0){
//说明还有子进程活着
break;
}else if(ret == -1){
//没有子进程,说明都回收完毕
break;
}
}
}
int main(){
//提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册好信号捕捉
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGCHLD);
sigprocmask(SIG_BLOCK,&set,NULL);
pid_t pid;
//创建20个子进程
for(int i=0;i<20;++i){
pid = fork();
if(pid == 0){
//禁止子进程来创建子进程
break;
}
}
if(pid > 0){
//父进程
//捕捉子进程死亡时发送的SIGCHLD信号,
//并进行回收(其他时间做自己的事情)
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
//注册信号捕捉
sigaction(SIGCHLD,&act,NULL);
//注册完信号捕捉以后,解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1){
printf("parent process, pid : %d\n",getpid());
sleep(2);
}
}else if(pid == 0){
//子进程
printf("child process,pid : %d\n",getpid());
}
return 0;
}
28 共享内存
01 共享内存
02 共享内存使用步骤
03 共享内存操作函数
共享内存相关的函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。
新创建的内存段中的数据都会被初始化为0
- 参数:
- key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。
一般使用16进制表示,非0值
- size: 共享内存的大小(共享内存以页为单位,size=5,就是有5页,linux的一页等于4kb,5页就是20kb)
- shmflg: 属性
- 访问权限
- 附加属性:创建/判断共享内存是不是存在
- 创建:IPC_CREAT
- 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
IPC_CREAT | IPC_EXCL | 0664
- 返回值:
失败:-1 并设置错误号
成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 功能:和当前的进程进行关联
- 参数:
- shmid : 共享内存的标识(ID),由shmget返回值获取
- shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定
- shmflg : 对共享内存的操作
- 读 : SHM_RDONLY, 必须要有读权限
- 读写: 0
- 返回值:
成功:返回共享内存的首(起始)地址。 失败(void *) -1
int shmdt(const void *shmaddr);
- 功能:解除当前进程和共享内存的关联
- 参数:
shmaddr:共享内存的首地址
- 返回值:成功 0, 失败 -1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进程 被销毁了对共享内存是没有任何影响。
- 参数:
- shmid: 共享内存的ID
- cmd : 要做的操作
- IPC_STAT : 获取共享内存的当前的状态
- IPC_SET : 设置共享内存的状态
- IPC_RMID: 标记共享内存被销毁
- buf:需要设置或者获取的共享内存的属性信息
- IPC_STAT : buf存储数据
- IPC_SET : buf中需要初始化数据,设置到内核中
- IPC_RMID : 没有用,NULL
key_t ftok(const char *pathname, int proj_id);
- 功能:根据指定的路径名,和int值,生成一个共享内存的key
- 参数:
- pathname:指定一个存在的路径
/home/nowcoder/Linux/a.txt
/
- proj_id: int类型的值,但是这系统调用只会使用其中的1个字节
范围 : 0-255 一般指定一个字符 'a'
问题1:操作系统如何知道一块共享内存被多少个进程关联?
- 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch
- shm_nattach 记录了关联的进程个数
问题2:可不可以对共享内存进行多次删除 shmctl
- 可以的
- 因为shmctl 标记删除共享内存,不是直接删除
- 什么时候真正删除呢?
当和共享内存关联的进程数为0的时候,就真正被删除
- 当共享内存的key为0的时候,表示共享内存被标记删除了
如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。
共享内存和内存映射的区别
1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
2.共享内存效果更高
3.内存
所有的进程操作的是同一块共享内存。
内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。
4.数据安全
- 进程突然退出
共享内存还存在
内存映射区消失
- 运行进程的电脑死机,宕机了
数据存在在共享内存中,没有了
内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。
5.生命周期
- 内存映射区:进程退出,内存映射区销毁
- 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机
如果一个进程退出,会自动和共享内存进行取消关联。
write_shm.c
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>
int main(){
//1、创建一个共享内存
int shmid = shmget(100,4096,IPC_CREAT | 0664);
printf("shmid : %d \n",shmid);
//2、和当前进程进行关联
void *ptr = shmat(shmid,NULL,0);
//3、写数据
char *buf = "hello,world";
memcpy(ptr,buf,strlen(buf)+1);
printf("按任意键继续!\n");
getchar();
//4、解除关联
shmdt(ptr);
//5、删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
read_shm.c
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>
int main(){
//1、获取一个共享内存
int shmid = shmget(100,0,IPC_CREAT);
printf("shmid : %d \n",shmid);
//2、和当前进程进行关联
void *ptr = shmat(shmid,NULL,0);
//3、读数据
printf("%s \n",(char *)ptr);
printf("按任意键继续!\n");
getchar();
//4、解除关联
shmdt(ptr);
//5、删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
04 共享内存操作命令
29 守护进程
01 终端
两个指令
tty
命令
Linux tty
命令用于显示终端机连接标准输入设备的文件名称。
echo $
命令
- echo $$ 返回登录shell的PID
- echo $? 返回上一个命令的状态,0表示没有错误,其它任何值表明有错误
- echo $# 返回传递到脚本的参数个数
- echo $* 以一个单字符串显示所有向脚本传递的参数,与位置变量不同,此选项参数可超过9个
- echo $! 返回后台运行的最后一个进程的进程ID号
- echo $@ 返回传递到脚本的参数个数,但是使用时加引号,并在引号中返回每个参数
- echo $- 显示shell使用的当前选项
- echo $0 是脚本本身的名字
- echo $_ 是保存之前执行的命令的最后一个参数
- echo $1 传入脚本的第一个参数
- echo $2 传入脚本的第二个参数
02 进程组
03 会话
04 进程组、会话、控制终端之间的关系
05 进程组、会话操作函数
06 守护进程
07 守护进程的创建步骤
/*
写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中
*/
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/time.h>
#include<signal.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>
void work(int num){
//捕捉到信号之后,获取系统时间,写入磁盘文件
time_t tm = time(NULL);
//localtime(&tm)可以把time_t格式的时间转化为本地的时间
struct tm *loc = localtime(&tm);
// char buf[1024];
// sprintf(buf,"%d-%d-%d %d:%d:%d",loc->tm_year,loc->tm_mon,
// loc->tm_mday,loc->tm_hour,loc->tm_min,loc->tm_sec);
// printf("%s \n",buf);
char *str = asctime(loc);
int fd = open("time.txt",O_RDWR | O_CREAT | O_APPEND,0664);
write(fd,str,strlen(str));
close(fd);
}
int main(){
//1、创建子进程,退出父进程
pid_t pid = fork();
if(pid > 0){
exit(0);
}
//2、将子进程重新创建一个会话
setsid();
//3、设置掩码
umask(022);
//4、更改工作目录
chdir("/home/nowcoder/");
//5、关闭、重定向文件描述符
//本案例没有打开文件,所以只需要重定向文件描述符
int fd = open("/dev/null",O_RDWR);
dup2(fd,STDIN_FILENO);
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
//6、业务逻辑
//捕捉定时信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM,&act,NULL);
struct itimerval val;
//延迟多少s开始
val.it_value.tv_sec = 2;
val.it_value.tv_usec = 0;
//间隔2s
val.it_interval.tv_sec = 2;
val.it_interval.tv_usec = 0;
//创建定时器
setitimer(ITIMER_REAL,&val,NULL);
//不让进程结束
while(1){
sleep(10);
}
return 0;
}
守护进程挺重要的,需要掌握