进程概述
ulimit -a查看资源上限
进程状态转换
ps aux/ajx 中 stat
kill -9/-SIGKILL ID 强制杀死
进程创建
示例:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
//创建子进程
pid_t pid = fork();
//判断是父进程还是子进程
if(pid > 0)
{
printf("pid : %d\n",pid);
//如果大于0,返回的是创建的子进程的进程号,当前是在父进程中
printf("i am parent process, pid : %d, ppid : %d\n",getpid(),getppid());
}
else if(pid == 0)
{
//当前是在子进程中
printf("i am child process, pid : %d, ppid : %d\n",getpid(),getppid());
}
//父子进程共享代码:
for(int i=0;i<5;i++)
{
printf("pid:%d , i:%d\n",getpid(),i);
sleep(1);
}
return 0;
}
父子进程虚拟地址空间情况
Linux的fork()函数使用写时拷贝(copy-on-write)实现。
写时拷贝可以推迟甚至避免拷贝数据。
fork()时,内核不复制整个进程的地址空间,而是让父子进程共享同一个地址空间,只有在需要写入时才会复制地址空间,让各个进程拥有各自的地址空间。
也就是说,资源的复制在需要写入的时候才会进行,在此之前,都以只读方式共享。
fork()后父子进程共享文件,fork产生的子进程和父进程通过文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。
父子进程关系及GDB多进程调试
父子进程之间的关系:
区别:
1.fork()函数的返回值不同
父进程中:>0 返回的是子进程的ID
子进程中:=0
2.pcb中的一些数据
当前的进程的id:pid
当前的进程的父进程的id:ppid
信号集
共同点:
某些状态下,子进程刚被创建出来,还没执行任何的写数据操作
-用户区的数据
-文件描述符表
父子进程对变量是否共享?
-刚开始的时候,是一样的,共享的,如果修改了数据,就不共享了
-读时共享(子进程被创建,两个进程没有做任何的写操作),写时拷贝
show follow-fork-mode
show detach-on-fork
exec函数族
一般从父进程fork出子进程后,在子进程中exec
最后一个是linux函数,前6个都是标准C库函数
execl
//需要头文件
#include<unistd.h>
//函数原型
int execl( const char *path, const char *arg, .../* (char *) NULL */);
/*
参数:
path:需要指定执行的文件的路径或者名称
arg:是执行可执行文件所需要的参数列表
第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
从第二个参数开始往后,就是程序执行所需要的参数列表
参数左后需要以NULL结束(哨兵)
返回值:
只有失败时返回-1,并设置errno
调用成功则无返回值
*/
示例:
//execl.c:
#include<unistd.h>
#include<stdio.h>
int main()
{
//创建一个子进程,在子进程中执行exec函数族中的函数
pid_t pid = fork();
if(pid > 0)
{
printf("i m parent process, pid:%d\n",getpid());
}
else if(pid == 0)
{
execl("/bin/ps","ps","aux",NULL);//需要写路径,子进程执行ps -aux,输出查看进程
perror("execl");
printf("i m child process\n");//无法执行,子进程被替换了
}
for(int i=0;i<3;i++)//父进程执行;子进程无法执行,子进程被替换了
{
printf("i=%d,pid=%d\n",i,getpid());
}
return 0;
}
execlp
//需要头文件
#include<unistd.h>
//函数原型
int execlp( const char *file, const char *arg, .../* (char *) NULL */);
/*
会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功
参数:
path:需要指定执行的文件名
arg:是执行可执行文件所需要的参数列表
第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
从第二个参数开始往后,就是程序执行所需要的参数列表
参数左后需要以NULL结束(哨兵)
返回值:
只有失败时返回-1,并设置errno
调用成功则无返回值
*/
示例:
//execl.c:
#include<unistd.h>
#include<stdio.h>
int main()
{
//创建一个子进程,在子进程中执行exec函数族中的函数
pid_t pid = fork();
if(pid > 0)
{
printf("i m parent process, pid:%d\n",getpid());
}
else if(pid == 0)
{
execlp("ps","ps","aux",NULL);//execlp直接文件名,不需要路径
perror("execlp");
printf("i m child process\n");//无法执行,子进程被替换了
}
for(int i=0;i<3;i++)//父进程执行;子进程无法执行,子进程被替换了
{
printf("i=%d,pid=%d\n",i,getpid());
}
return 0;
}
进程退出,孤儿进程,僵尸进程
第一个是标准c库的,第二个是linux的
常用第一个,做的事情更多
status参数:是进程退出时的一个状态信息,父进程回收子进程资源的时候可以获取到。
示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
printf("hello\n");
//\n会刷新IO缓冲区,将hello打印出来
printf("world");
//没有刷新缓冲区,只是将world放入IO缓冲区
//exit(0);
//调用标准C库exit,会刷新缓冲区,将world打印出来
_exit(0);
//调用Linux _exit,不刷新缓冲区,不打印
return 0;
}
示例:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
//创建子进程
pid_t pid = fork();
//判断是父进程还是子进程
if(pid > 0)
{
printf("pid : %d\n",pid);
//如果大于0,返回的是创建的子进程的进程号,当前是在父进程中
printf("i am parent process, pid : %d, ppid : %d\n",getpid(),getppid());
}
else if(pid == 0)
{
sleep(1);//睡1秒,让父进程死掉
//当前是在子进程中
printf("i am child process, pid : %d, ppid : %d\n",getpid(),getppid());
//打印ppid:1,因为父进程死了,由进程为1的进程领养并回收
}
for(int i=0;i<5;i++)
{
printf("pid:%d , i:%d\n",getpid(),i);
}
return 0;
}
进程回收
wait
//需要头文件
#include<sys/types.h>
#include<sys/wait.h>
//函数原型
pid_t wait(int *wstatus);
/*
功能:等待任意一个子进程结束,如果任意一个子进程结束,此函数会回收子进程的资源
参数:int *wstatus
进程退出时的状态信息,传入的是一个int类型的地址,传出参数
返回值:
成功,返回被回收的子进程的id
失败,返回-1(所有子进程都结束,或调用函数失败)
调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时
才被唤醒(相当于继续往下执行)
如果没有子进程了,函数立即返回,返回-1,如果子进程都已经结束了,也立即返回-1
*/
示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
//有一个父进程
pid_t pid;
//创建5个子进程(兄弟)
for(int i=0;i<5;i++)
{
pid=fork();
if(pid == 0)break;//子进程不要fork
}
if(pid > 0)
{
//父进程
while(1)
{
printf("parent,pid=%d\n",getpid());
//int ret = wait(NULL);
int st;
int ret = wait(&st);
if(ret == -1)break;
if(WIFEXITED(st))
{
//是否正常退出
printf("退出的状态码:%d\n",WEXITSTATUS(st));
}
if(WIFSIGNALED(st))
{
//是否异常终止
printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
}
printf("chile die, pid=%d\n",ret);
sleep(1);
}
}
else if (pid == 0)
{
//子进程
printf("child,pid=%d\n",getpid());
sleep(1);
exit(0);//其中的参数为退出的状态码
}
return 0;
}
waitpid
//需要头文件
#include<sys/types.h>
#include<sys/wait.h>
//函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
/*
功能:回收指定进程号的子进程,可以设置是否阻塞
参数:
-pid_t pid:
pid>0:某个要回收的子进程的pid
pid=0:回收当前进程组的所以子进程 (也会用)
pid=-1:回收所有的子进程,相当于wait() (最常用)
pid<-1:某个进程组的组id的绝对值,回收指定进程组中的子进程
-int *wstatus:
进程退出时的状态信息,传入的是一个int类型的地址,传出参数
-int options:
设置阻塞或非阻塞
0:阻塞
WNOHANG:非阻塞
当设置为waitpid( -1, &wstatus, 0 );时,相当于wait(&wstatus)函数
返回值:
>0:返回被回收的子进程的id
=0:options=WNOHANG,表示还有子进程活着
=-1:错误,或没有子进程了
*/
示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
//有一个父进程
pid_t pid;
//创建5个子进程(兄弟)
for(int i=0;i<5;i++)
{
pid=fork();
if(pid == 0)break;//子进程不要fork
}
if(pid > 0)
{
//父进程
while(1)
{
printf("parent,pid=%d\n",getpid());
sleep(1);
//int ret = wait(NULL);
int st;
int ret = waitpid(-1,&st,WNOHANG);
if(ret == -1)break;
else if(ret == 0)
{
//说明还有子进程存在
continue;
}
else if(ret>0)
{
if(WIFEXITED(st))
{
//是否正常退出
printf("退出的状态码:%d\n",WEXITSTATUS(st));
}
if(WIFSIGNALED(st))
{
//是否异常终止
printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
}
printf("chile die, pid=%d\n",ret);
}
}
}
else if (pid == 0)
{
//子进程
printf("child,pid=%d\n",getpid());
sleep(1);
exit(0);//其中的参数为退出的状态码
}
return 0;
}
进程间通信
匿名管道
pipe
//需要头文件
#include<unistd.h>
//函数原型
int pipe(int pipefd[2]);
/*
作用:创建一个匿名管道,用来进程间通信
参数:
int pipefd[2]:传出参数
pipefd[0]对应管道读端
pipefd[1]对应管道写端
返回值:
成功:0
失败:-1 并改errno
管道默认是阻塞的,如果管道中没有数据,read阻塞,如果管道满了,write阻塞
注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
示例:
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//父进程读,子进程写
int main()
{
//在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1)
{
perror("pipe");
exit(0);
}
//创建子进程
pid_t pid = fork();
if(pid > 0)
{//父进程
printf("i am parent process,pid:%d\n",getpid());
//关闭写端;防止自己读自己写
close(pipefd[1]);
char buf[1024]={0};
while(1)
{
//从管道的读取端获取数据
int len = read(pipefd[0],buf,sizeof(buf));
printf("parent recv : %s,pid : %d\n",buf,getpid());
//向管道中写入数据
//char *str = "hello, i am parent";
//write(pipefd[1],str,strlen(str));
//sleep(1);
}
}
else if(pid == 0)
{//子进程
printf("i am child process,pid:%d\n",getpid());
//关闭读端
close(pipefd[0]);
//char buf[1024]={0};
while(1)
{
//向管道中写入数据
char *str = "hello, i am child";
write(pipefd[1],str,strlen(str));
//sleep(1);
//从管道的读取端获取数据
//int len = read(pipefd[0],buf,sizeof(buf));
//printf("child recv : %s,pid : %d\n",buf,getpid());
//bzero(buf,1024);
}
}
return 0;
}
fpathconf
示例:
#include<unistd.h>
#include<stdio.h>
int main()
{
int pipefd[2];
int ret = pipe(pipefd);
//获取管道的大小
long size = fpathconf(pipefd[0],_PC_PIPE_BUF);
printf("pipe size: %ld\n",size);
return 0;
}
案例:
/*
实现 ps aux | grep xxx 父子进程间通信
子进程:ps aux,子进程结束后,将数据发给父进程
父进程:获取数据,过滤
pipe()
execlp()
子进程将标准输出stdout_fileno重定向到管道的写端。dup2
*/
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<wait.h>
int main()
{
//创建一个管道
int fd[2];
int ret = pipe(fd);
if(ret==-1)
{
perror("pipe");
exit(0);
}
//创建子进程
pid_t pid = fork();
if(pid>0)
{ //父进程
//关闭写端
close(fd[1]);
//从管道中获取
char buf[1024]={0};
int len = -1;
while((len = read(fd[0],buf,sizeof(buf)-1))>0)
{
//过滤数据输出
printf("%s",buf);
memset(buf,0,1024);
}
wait(NULL);
}
else if(pid==0)
{ //子进程
//关闭读端
close(fd[0]);
//文件描述符的重定向 stdout_fileno -> fd[1]
dup2(fd[1],STDOUT_FILENO);
//执行ps aux
execlp("ps","ps","aux",NULL);
perror("execlp");
exit(0);
}
else
{
perror("fork");
exit(0);
}
}
管道的读写特点
使用管道时,应注意以下几种特殊的情况(假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),
有进程从管道的读端读数据,那么管道中剩余的数据被读取以后,
再次read会返回0,就像读到文件末尾一样
2.如果有指向管道写端的文件描述符没有关闭(管道写端引用计数>0),
而持有管道写端的进程也没有往管道中写数据,这个时候,
有进程从管道中读取数据,那么管道中剩余的数据被读取后,
再次read会阻塞,直到管道中有数据可以读了,才读取数据并返回
3.如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),
这个时候有进程向管道中写数据,那么该进程会收到一个信号SIGPIPE,
通常会导致进程异常终止
4.如果有指向管道读端的文件描述符没有关闭了(管道读端引用计数>0),
而持有管道读端的进程也没有从管道中读数据,这时,
有进程向管道中写数据,那么在管道被写满的时候,再次write会阻塞
直到管道中有空位置才能再次写入并返回
总结:
读管道:
管道中有数据:read返回实际读到的字节数
管道中无数据:
写端被全部关闭,read返回0,(相当于读到文件末尾)
写端没有被完全关闭,read阻塞等待
写管道:
管道读端全部被关闭:进程异常终止(进程收到SIGPIPE信号)
管道读端没有被全部关闭:
管道已满:write阻塞
管道没满:write将数据写入,并返回实际写入的字节数
管道设置为非阻塞
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
/*
设置管道非阻塞
int flags = fcntl(fd[0],F_GETFL);获取原来的flag
flags |= O_NONBLOCK;修改flag
fcntl(fd[0],F_SETFL,flags);设置新的flag
*/
int main()
{
//在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1)
{
perror("pipe");
exit(0);
}
//创建子进程
pid_t pid = fork();
if(pid > 0)
{//父进程
printf("i am parent process,pid:%d\n",getpid());
//关闭写端;防止自己读自己写
close(pipefd[1]);
char buf[1024]={0};
int flags = fcntl(pipefd[0],F_GETFL); //获取原来的flag
flags |= O_NONBLOCK; //修改flag
fcntl(pipefd[0],F_SETFL,flags); //设置新的flag
while(1)
{
//从管道的读取端获取数据
int len = read(pipefd[0],buf,sizeof(buf));
printf("len: %d\n",len);
printf("parent recv : %s,pid : %d\n",buf,getpid());
memset(buf,0,1024);
sleep(1);
}
}
else if(pid == 0)
{//子进程
printf("i am child process,pid:%d\n",getpid());
//关闭读端
close(pipefd[0]);
while(1)
{
//向管道中写入数据
char *str = "hello, i am child";
write(pipefd[1],str,strlen(str));
sleep(5);
}
}
return 0;
}
有名管道
mkfifo
//需要头文件
#include<sys/types.h>
#include<sys/stat.h>
//函数原型
int mkfifo(const char *pathname, mode_t mode);
/*
参数:
-pathname:管道名称的路径
-mode:文件的权限,和open的mode一样
返回值:
成功:0
失败:-1 并设置errno
注意:
1.一个为只读而打开一个管道的进程会阻塞,直到另一个进程为写而打开管道
2.一个为只写而打开一个管道的进程会阻塞,直到另一个进程为读而打开管道
读管道:
管道中有数据,read返回实际读到的字节数
管道中无数据:
管道写端被全部关闭,read返回0(相当于读到文件末尾)
写端没有全部关闭,read阻塞等待
写管道:
管道读端被全部关闭,进程异常终止(收到一个SIGPIPE信号)
管道读端没有全部关闭:
管道已经满了,write会阻塞
管道没有满,write将数据写入,并返回实际写入的字节数
*/
示例write.c read.c
//write.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
//向管道中写数据
int main()
{
//1.判断管道是否存在
int ret = access("test",F_OK);
if(ret == -1)
{
printf("管道不存在,创建管道\n");
//2.创建管道文件
ret = mkfifo("test",0664);
if(ret == -1)
{
perror("mkfifo");
exit(0);
}
}
//3.以只写方式打开管道
int fd = open("test",O_WRONLY);
if (fd==-1)
{
perror("open");
exit(0);
}
//4.写数据
for(int i=0;i<100;i++)
{
char buf[1024];
sprintf(buf,"hello, %d\n",i);
printf("write data: %s\n",buf);
write(fd,buf,strlen(buf));
sleep(1);
}
close(fd);
return 0;
}
//read.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
//从管道中读数据
int main()
{
//1.只读方式打开管道文件
int fd = open("test",O_RDONLY);
if (fd == -1)
{
perror("open");
exit(0);
}
//2.读数据
while(1)
{
char buf[1024]={0};
int len = read(fd,buf,sizeof(buf));
if(len == 0)
{
printf("写端断开连接了...\n");
break;
}
printf("recv buf : %s\n",buf);
}
close(fd);
return 0;
}
使用有名管道完成聊天功能
chatA.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
//1.判断有名管道文件是否存在
int ret = access("fifo1",F_OK);
if(ret == -1)
{ //文件不存在
printf("管道不存在,创建对应有名管道\n");
ret = mkfifo("fifo1",0664);
if(ret == -1)
{
perror("mkfifo");
exit(0);
}
}
ret = access("fifo2",F_OK);
if(ret == -1)
{ //文件不存在
printf("管道不存在,创建对应有名管道\n");
ret = mkfifo("fifo2",0664);
if(ret == -1)
{
perror("mkfifo");
exit(0);
}
}
//2.以只写方式打开管道fifo1
int fdw = open("fifo1",O_WRONLY);
if(fdw == -1)
{
perror("open");
exit(0);
}
printf("打开管道fifo1成功,等待写入...\n");
//3.以只读方式打开管道fifo2
int fdr = open("fifo2",O_RDONLY);
if(fdr == -1)
{
perror("open");
exit(0);
}
printf("打开管道fifo2成功,等待读取...\n");
//4.循环写读数据
char buf[128];
while(1)
{
//写数据
memset(buf,0,128);
//获取标准输入的数据
fgets(buf,128,stdin);
ret = write(fdw,buf,strlen(buf));
if(ret == -1)
{
perror("write");
exit(0);
}
//读数据
memset(buf,0,128);
ret = read(fdr,buf,128);
if(ret <= 0)
{
perror("read");
break;
}
printf("buf:%s\n",buf);
}
//关闭文件描述符
close(fdr);
close(fdw);
return 0;
}
chatB.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
//1.判断有名管道文件是否存在
int ret = access("fifo1",F_OK);
if(ret == -1)
{ //文件不存在
printf("管道不存在,创建对应有名管道\n");
ret = mkfifo("fifo1",0664);
if(ret == -1)
{
perror("mkfifo");
exit(0);
}
}
ret = access("fifo2",F_OK);
if(ret == -1)
{ //文件不存在
printf("管道不存在,创建对应有名管道");
ret = mkfifo("fifo2",0664);
if(ret == -1)
{
perror("mkfifo");
exit(0);
}
}
//2.以只读方式打开管道fifo1
int fdr = open("fifo1",O_RDONLY);
if(fdr == -1)
{
perror("open");
exit(0);
}
printf("打开管道fifo1成功,等待读取...\n");
//3.以只写方式打开管道fifo2
int fdw = open("fifo2",O_WRONLY);
if(fdw == -1)
{
perror("open");
exit(0);
}
printf("打开管道fifo2成功,等待写入...\n");
//4.循环读写数据
char buf[128];
while(1)
{
//读数据
memset(buf,0,128);
ret = read(fdr,buf,128);
if(ret <= 0)
{
perror("read");
break;
}
printf("buf:%s\n",buf);
//写数据
memset(buf,0,128);
//获取标准输入的数据
fgets(buf,128,stdin);
ret = write(fdw,buf,strlen(buf));
if(ret == -1)
{
perror("write");
exit(0);
}
}
//关闭文件描述符
close(fdr);
close(fdw);
return 0;
}
内存映射
//需要头文件
#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<sys/mman.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<wait.h>
#include<stdlib.h>
int main()
{
//1.打开一个文件
int fd = open("test.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");
exit(0);
}
//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!!!");
}
//关闭内存映射区
munmap(ptr,size);
}
注意事项:
用内存映射做小文件的拷贝
//使用内存映射实现文件拷贝的功能
/*
思路:
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<stdlib.h>
#include<string.h>
int main()
{
//1.对原始的文件进行内存映射
int fd = open("english.txt",O_RDWR);
if(fd == -1)
{
perror("open");
exit(0);
}
//获取原文件的大小
int len = lseek(fd,0,SEEK_END);
//2.创建一个新文件(扩展该文件)
int fd1 = open("cpy.txt",O_RDWR | O_CREAT,0664);
if(fd1 == -1)
{
perror("open");
exit(0);
}
//扩展
truncate("cpy.txt",len);
write(fd1," ",1);
//3.分别做内存映射
void* ptr = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
void* ptr1 = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
if(ptr==MAP_FAILED)
{
perror("mmap");
exit(0);
}
if(ptr1==MAP_FAILED)
{
perror("mmap");
exit(0);
}
//4.内存拷贝
memcpy(ptr1,ptr,len);
//5.释放资源
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<stdlib.h>
#include<string.h>
#include<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");
exit(0);
}
//2.父子进程间通信
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);
}
//3.释放内存映射区
int ret = munmap(ptr,len);
if(ret == -1)
{
perror("munmap");
exit(0);
}
return 0;
}
信号
kill、raise、abort、alarm、setitimer函数
//需要头文件
#include<sys/types.h>
#include<signal.h>
//函数原型
int kill(pid_t pid, int sig);
/*
功能:给任何的进程或者进程组pid,发送任何的信号sig
参数:
-pid:
>0:将信号发送给指定的进程
=0:将信号发送给当前的进程组
=-1:将信号发送给每一个有权限接收这个信号的进程
<-1:这个pid=某个进程组的ID取反
-sig:需要发送的信号的编号或者是宏值,0表示不发送任何信号
kill(getpid(),9);
*/
//需要头文件
#include<signal.h>
//函数原型
int raise(int sig);
/*
功能:给当前进程发送信号
参数:
-sig:要发送的信号
-返回值:
成功:0
失败:非0
kill(getpid(),sig);
*/
//需要头文件
#include<stdlib.h>
//函数原型
void abort(void);
/*
功能:发送SIGABRT信号给当前进程,杀死当前进程
kill(getpid(),ABRT);
*/
//需要头文件
#include<unistd.h>
//函数原型
unsigned int alarm(unsigned int seconds);
/*
功能:设置定时器(闹钟),函数调用,开始倒计时,当倒计时为0时,函数会给当前进程发送一个信号:SIGALRM
参数:
seconds:倒计时的时长,单位:秒,如果参数为0,表示定时器无效(不倒计时,不发信号)。
取消一个定时器,通过alarm(0)
返回值:
之前没有定时器,返回0
之前有定时器,返回之前的定时器剩余的时间
SIGALARM:默认终止当前的进程,每一个进程都有且只有唯一的一个定时器
alarm(10); -->返回0
过1秒
alarm(5); -->返回9,覆盖前面的定时器
alarm(100) -->该函数是不阻塞的
定时器与进程的状态无关(自然定时法),无论进程处于什么状态,alarm都会计时
*/
//需要头文件
#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秒定时1次
-old_value:
记录上一次的定时的时间参数,一般不使用,NULL
返回值:
成功:0
失败:-1,并设置错误号
非阻塞
struct itimerval new_value;
设置间隔的时间
new_value.it_interval.tv_sec=2;
new_value.it_interval.tv_usec=0;
设置延迟的时间,3秒后开始第一次定时
new_value.it_value.tv.sec=3;
new_value.it_value.tv.usec=0;
setitimer(ITIMER_REAL,&new_value,NULL);
*/
signal信号捕捉函数
//需要头文件
#include<signal.h>
//函数原型
typedef void (*sighandler_t)(int);
sighander_t signal(int signum,sighandler_t handler);
/*
功能:
设置某个信号的捕捉行为
参数:
signum:要捕捉的信号
handler:捕捉到信号要如何处理
-SIG_IGN:忽略信号
-SIG_DFL:使用信号的默认行为
-回调函数:这个函数是内核调用,程序员只负责写:捕捉到信号后如何处理
回调函数:
-需要程序员实现,提前准备好的,函数类型根据实际需求,看函数指针的定义
-不是程序员调用,而是当信号产生,有框架(此处内核)调用
-函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了
返回值:
成功:返回上一次注册的信号处理函数的地址,第一次调用返回NULL
失败:返回SIG_ERR,设置错误号
SIGKILL SIGSTOP不能被捕捉,不能被忽略
*/
signal示例:
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
//过3秒,每隔2秒定一次时
//回调函数
void myalarm(int num)
{
printf("捕捉到信号,编号:%d\n",num);
printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
}
int main()
{
//先注册信号捕捉
//signal(SIGALRM,SIG_IGN);//忽略
//signal(SIGALRM,SIG_DFL);//默认中止
//void (*sighandler_t)(int);函数指针,int型参数表示捕捉到的信号编号
signal(SIGALRM,myalarm);//使用回调函数
struct itimerval new_value;
//设置间隔的时间
new_value.it_interval.tv_sec=2;
new_value.it_interval.tv_usec=0;
//设置延迟的时间,3秒后开始第一次定时
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");
exit(0);
}
getchar();
return 0;
}
信号集
以下信号集相关的函数都是对自定义的信号集进行操作:
//需要头文件
#include<signal.h>
//函数原型
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>
//-std=gnu99
int main()
{
//创建一个信号集
sigset_t set;
//清空信号集中的内容
sigemptyset(&set);
//判断SIGINT是否在信号集中
int ret = sigismember(&set,SIGINT);
if(ret == 0)
{
printf("SIGINT 不阻塞\n");
}
else if(ret == 1)
{
printf("SIGINT 阻塞\n");
}
//添加几个信号到信号集中
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//判断SIGINT是否在信号集中
ret = sigismember(&set,SIGINT);
if(ret == 0)
{
printf("SIGINT 不阻塞\n");
}
else if(ret == 1)
{
printf("SIGINT 阻塞\n");
}
//从信号集中删除一个信号
sigdelset(&set,SIGINT);
//判断SIGINT是否在信号集中
ret = sigismember(&set,SIGINT);
if(ret == 0)
{
printf("SIGINT 不阻塞\n");
}
else if(ret == 1)
{
printf("SIGINT 阻塞\n");
}
return 0;
}
//需要头文件
//函数原型
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*
功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
参数:
-how:如何对内核阻塞信号集进行处理
SIG_BLOCK:将用户设置的阻塞信号添加到内核中,内核中原来的数据不变
假设内核中默认的阻塞信号集是mask. mask | set
SIG_UNBLOCK:根据用户设置的数据,对内核中的数据进行解除阻塞
假设内核中默认的阻塞信号集是mask. 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");
exit(0);
}
}
printf("\n");
sleep(1);
if(num==10)
{
//解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
}
}
return 0;
}
sigaction信号捕捉函数(多使用sigaction,少使用signal)
//需要头文件
#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; //临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号
int sa_flags; //使用哪一个信号处理对捕捉到的信号进行处理,
//可以是0,表示使用sa_handler,也可以是SA_SIGINFO,表示使用sa_sigaction
void (*sa_restorer)(void); //被废弃了,NULL
}
示例:
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
//过3秒,每隔2秒定一次时
//回调函数
void myalarm(int num)
{
printf("捕捉到信号,编号:%d\n",num);
printf("xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
}
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;
//设置延迟的时间,3秒后开始第一次定时
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");
exit(0);
}
//getchar();
while(1){}
return 0;
}
SIGCHLD信号
使用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的资源
//while (1)
//{
// 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;
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("chlid process pid:%d\n",getpid());
}
}
共享内存
//需要头文件
#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:共享内存的大小
-shmflg:属性
-访问权限
-附加属性:创建/判断共享内存是不是存在
-创建:IPC_CREAT
-判断共享内存是否存在:IPC_EXCL,需要和IPC_CREAT一起使用
IPC_EXCL | IPC_CREAT |0664
返回值:
失败:-1,并设置errno
成功:>0,返回共享内存的引用的ID,后面操作共享内存都是通过这个值
*/
//需要头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
功能:和当前的进程进行关联
参数:
-shmid:共享内存的标识(ID),由shmget返回值获取
-shmadrr:申请的共享内存的起始地址,可设为NULL,由内核来指定
-shmflg:对共享内存的操作
-读:SHM_RDONLY,必须要有读权限
-读写:0
返回值:
成功:返回共享内存的首(起始)地址
失败:(void*)-1
*/
//需要头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
int shmdt(const void *shmaddr);
/*
功能:解除当前进程和共享内存的关联
参数:
shmaddr:共享内存的首地址
返回值:
成功:0
失败:-1
*/
//需要头文件
#include <sys/types.h>
#include <sys/shm.h>
//函数原型
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
*/
//需要头文件
#include <sys/types.h>
#include <sys/ipc.h>
//函数原型
key_t ftok(const char *pathname, int proj_id);
/*
功能:根据指定的路径名和int值,生成一个共享内存的key
参数:
-pathname:指定一个存在的路径
-proj_id:int类型的值,但是这个系统调用只会使用其中的1个字节
范围:0-255 一般指定一个字符 'a'
*/
问题1:操作系统如何知道一块共享内存被多少个进程关联?
-共享内存维护了一个结构体struct shmid_ds ,这个结构体中有一个成员shm_nattach
-shm_nattach 记录了关联的进程个数
问题2:可不可以对共享内存进行多次删除 shmctl
-可以
-因为shmctl只是标记删除,不是真删除
-当和共享内存关联的进程数为0的时候,就真删除了
-共享内存的key为0的时候
-共享内存被标记删除了
-如果一个进程和共享内存取消关联,这个进程就不能继续操作这个共享内存,也不能再次关联
共享内存和内存映射的区别
1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
2.共享内存效率更高
3.内存
所有的进程操作的是同一块共享内存
内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存
4.数据安全
-进程突然退出
共享内存还存在
内存映射区消失
-运行进程的电脑死机,宕机
数据存在在共享内存中,没有了
内存映射区的数据,由于磁盘文件中的数据还在,所有内存映射区的数据还存在
5.生命周期
-内存映射区:进程退出,内存映射区销毁
-共享内存:进程退出,共享内存还在,手动标记删除(所有关联进程数为0),或者关机
如果一个进程退出,会自动和共享内存进行取消关联
守护进程
查看当前终端:
tty
查看当前终端的进程号:
echo $$
写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中
示例:
/*
写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中
*/
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.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);
struct tm *loc = localtime(&tm);
//char buf[1024];
//sprintf(buf,"%d-%d-%d %d:%d:%d\n",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/jeon/");
//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;
val.it_value.tv_sec = 2;
val.it_value.tv_usec = 0;
val.it_interval.tv_sec = 2;
val.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL,&val,NULL);
//不让进程结束
while(1)
{
sleep(10);
}
return 0;
}