Linux-多进程开发,看完就明白了
mhr18 2025-05-14 14:56 24 浏览 0 评论
/*
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
作用:创建子进程
返回值:
fork()返回值会返回两次,一次是在父进程中,一次是在子进程中
在父进程中返回创建的子进程的ID,
在子进程中,返回0
如何区分父进程和子进程,通过fork的返回值
在父进程中返回-1,表示创建子进程失败,并且设置errno
父子进程之间的关系:
区别:
1.fork()函数的返回值不同
父进程中:>0,返回子进程的PID
子进程中:=0
2.pcb中的一些数据
当前进程的id pid
当前进程的父进程的id ppid
信号集
共同点:
某些状态下,子进程刚被创建出来,还没有执行任何的写数据操作
-用户区的数据
-文件描述符表
父子进程对变量是否是共享的
-刚开始是一样的,如果修改类数据,就不共享了
读时共享(子进程刚创建),写时拷贝
父子进程之间不能用变量进行通信,它们之间互不影响
*/
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int num =10;
//创建子进程
pid_t pid = fork();//pid_t本质上是int类型的
//判断是父进程还是子进程
if(pid>0){
printf("pid : %d\n", pid);
//如果大于0,返回的是创建的子进程的id,当前是父进程
printf("i am parent process,pid : %d, ppid:%d\n",getpid(),getppid());
printf("parent num: %d\n",num);
num+=10;
printf("parent num+=10: %d\n",num);
}else if(pid == 0)
{
//当前是子进程
printf("i am child process, pid :%d,ppid : %d\n",getpid(),getppid());
printf("child num: %d\n",num);
num+=100;
printf("child num+=100: %d\n",num);
}
for(int i =0;i<3;i++)//默认父子进程都会去执行
{
printf("i : %d\n", i);
sleep(1);
}
return 0;
}
exec函数族
可执行文件的用户区去替换进程中的用户区,一般情况下会创建一个子进程,在子进程创建exec函数族中的函数
查看execl
execlp
/*#include <unistd.h>
extern char **environ;
int execlp(const char *file, const char *arg, ...);
-会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就
参数:-file:需要指定的执行的可执行文件的文件名
a.out ps
-arg:是执行可执行文件所需要的参数列表
第一个参数一般没有什么作用,为了方便一般写的执行的参数的名称,从第二个人参数开始往后,就是程序执行所需要的参数列表
参数最后需要以NULL结束(哨兵)
返回值:-仅仅在出错的时候返回,返回-1,并且设置errno
如果调用成功,就没有返回值
*/
#include <unistd.h>
#include <stdio.h>
int main()
{
//创建一个子进程在子进程中执行exec函数族中的函数
pid_t pid =fork();
if(pid >0){
//父进程
printf("i am parent process,pid : %d\n",getpid());
sleep(1);
}else if(pid == 0)
{
//子进程
execlp("ps","ps","aux",NULL);
printf("i am child process , pid:%d\n",getpid());
}
for(int i =0;i<3;i++){
printf("i=%d , pid = %d\n",i,getpid());
}
return 0;
}
可执行程序的程序名,第二个参数
进程控制
fork函数 读时共享,写时复制 主要目的是降低内存的使用
子进程退出,父进程能得到子进程退出的状态 父进程有义务回收子进程的资源
#include <stdlib.h>
#include<unistd.h>
#include <stdio.h>
int main()
{
printf("hello\n");
printf("world");
exit(0);
return 0; //和exit是一样的,都表示进程退出
}
如下,1领养了孤儿进程 ,由1进程回收资源
将exit()改成_exit()后 world在缓冲区里
孤儿进程
父进程运行结束,子进程还在运行,这样的子进程称为孤儿进程
每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表出面处理它的一切善后工作。
孤儿进程并不会有什么危害
如下,1领养了孤儿进程 ,由1进程回收资源
父进程结束后,显示了一个终端(回到前台)
为什么都显示在终端?父子进程的内核去的一些部分是共享的 文件描述符表前三个(0,1,2)标准输入,输出,错误
关于C/C++Linux后台服务器开发高级架构师知识点学习视频 点击 正在跳转 获取,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。
僵尸进程
每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要父进程去释放。
进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸
(Zombie)进程。
僵尸进程不能被kii -9杀死
僵尸进程 内核区数据没办法释放
如何解决呢?
ctrl+c 发送一个9号信号把进程杀死 杀死父进程
此时僵尸进程就不存在了 不过此方法只是在演示阶段使用,一般是wait 或waitpid
wait
#include <sys/types.h>
#include <sys/wait.h>
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
int main()
{
//有一个父进程,创建5个子进程(兄弟)
pid_t pid;
for(int i=0;i<5;i++) //这里不只产生5个
{
pid =fork();
if(pid ==0)
{
break; //此时子进程就不会产生孙子进程
}
}
if(pid >0)
{//父进程
while(1)
{
printf("i am 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("child die,pid =%d\n",ret); //如果成功了,返回值就是子进程的id,失败返回-1
sleep(1);
}
}
else if (pid ==0){
//子进程
while(1)
{
printf("child,pid = %d\n",getpid());
sleep(1);
}
exit(1);
}
return 0;
}
通过信号杀死
kill -9
默认是阻塞状态
非阻塞状态
int main()
{
//有一个父进程,创建5个子进程(兄弟)
pid_t pid;
for(int i=0;i<5;i++) //这里不只产生5个
{
pid =fork();
if(pid ==0)
{
break; //此时子进程就不会产生孙子进程
}
}
if(pid >0)
{//父进程
while(1)
{
printf("i am 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("child die,pid =%d\n",ret); //如果成功了,返回值就是子进程的id,失败返回-1
}
sleep(1);
}
}
else if (pid ==0){
//子进程
while(1)
{
printf("child,pid = %d\n",getpid());
sleep(1);
}
exit(1);
}
return 0;
}
父进程可继续往下执行
进程间通信
进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。
进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信(IPC)
03匿名管道
管道也叫无名(匿名)管道,它是是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制。
统计一个目录中文件的数目命令:ls | wc –l,为了执行该命令,shell 创建了两个进程来分别执行 ls 和 wc。
管道两端对应两个文件描述符支持读操作和写操作
一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少
通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的。
在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。 全双工:
半双工:就像对讲机 需要CSMA/CD
从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据,在管道中无法使用 lseek() 来随机的访问数据。
匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。
管道数据结构
循环队列
示例:
/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用来进程间通信
参数:int pipefd[2]这个数组是一个传出参数
Pipefd[0]对应的是管道的读端
pipefd[1]对应的是管道的写端
返回值: 成功返回0,失败返回-1
注意:匿名管道只能用于具有关系的进程间的通信(父子,兄弟)
*/
//子进程发送数据给父进程,父进程读取到数据输出
#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)
{
//父进程
//从管道的读取段读取数据
char buf[1024]={0};
int len = read(pipefd[0],buf,sizeof(buf));
printf("parent recv : %s,pid : %d\n",buf,getpid());
}
else if(pid == 0)
{
sleep(10);
//子进程
char* str = "hello,i am child";
write(pipefd[1],str,strlen(str));
}
return 0;
}
只有当管道里面有数据,才能去读,不然处于
父子进程相互读写
#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());
//父进程
//从管道的读取段读取数据
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)
{
sleep(10);
//子进程
printf("i am child process,pid : %d\n",getpid());
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());
}
}
return 0;
}
ulimit -a 管道大小:8块 512字节/块 4k
存在t的原因,是没有清除数组
为什么会出现这种情况?
自己写入的数据被自己读了
怎么解决这个问题? 父子进程要么读,要么写,一般情况下不会出现一个子进程又读又写
管道非阻塞
char buf[1024]={0};
int flags = fcntl(pipefd[0],F_GETFL);//获取原来的flag标记
flags |= O_NONBLOCK; //修改flag的值
fcntl(pipefd[0],F_SETFL,flags); //设置新的flag
有名管道
FIFO 提供了一个路径名与之关联 其打开方式与打开一个普通文件是一样的 只要可以访问该路径,就能够彼此通过FIFO通信
有名管道的使用
mkfifo 名字 严格遵循先进先出
方法1: 通过命令创建
方法2:
通过函数创建
int main()
{
int ret = mkfifo("fifo1",0664);
if(ret == -1)
{
perror("mkfifo");
exit(0);
}
return 0;
}
相关推荐
- 一文带您了解数据库的行列之争:行式与列式存储的异同
-
数据库存储格式是数据库管理系统中一个至关重要的方面,它直接影响到数据的组织和检索效率。在数据库中,有两种主要的存储格式,即行式存储和列式存储。这两者采用截然不同的方法来组织和存储数据,各自具有一系列优...
- NL2SQL(三)开源项目怎么选:talk is cheap, show me the code!
-
老规矩,先看效果下面的demo来自试用的SuperSonic,将会在下面详细介绍:大模型时代Text-to-SQL特点随着基于LLM技术的发展,RAG/AIAgent/Fine...
- JDK25长期支持版九月降临:18项王炸功能全解析
-
Java要放大招啦!9月份推出的JDK25长期支持版已经锁定18个超能力,从稳定值到结构化并发,还有Linux系统下的"预知未来"性能分析!下面我用打游戏的术语给你们掰扯明白:1、飞...
- OceanBase 推出单机版 高度兼容MySQL和Oracle
-
【环球网科技综合报道】3月27日,独立数据库厂商OceanBase正式发布单机版产品。据悉,这一产品基于自主研发的单机分布式一体化架构设计,具备极简数据库架构和高度兼容性,为中小规模业务提供兼具性能与...
- 黄远邦:应对7月1日闰秒对Oracle数据库影响
-
由于今年7月1日全世界会多出一秒,这可能对时间敏感的IT系统造成较大影响。中亦科技数据库团队对此问题做了深入的研究,并对用户系统提出了相应的解决方法及建议。中亦科技数据库产品总监黄远邦认为,闰秒调整会...
- MySQL数据库密码忘记了,怎么办?(mysql 数据库密码)
-
#头条创作挑战赛#MySQL数据库密码忘记了且没有其他可以修改账号密码的账户时怎么办呢?登录MySQL,密码输入错误/*密码错误,报如下错误*/[root@TESTDB~]#mysql-u...
- Chinese AI Talent in Spotlight as Nvidia and Meta Escalate Talent War
-
OntherightisBanghuaZhu,ChiefResearchScientistatNVIDIATMTPOST--SiliconValley’stoptech...
- 用Cursor开启JAVA+AI生涯(javascirpt怎么开启)
-
Cursor是基于VSCode开发的一款编辑器,支持多种语言的开发编辑。与传统的开发工具相比,它有多种优势:与AI无缝集成,响应速度快,占用内存小。但很多同学在"起步"过程中遇到了...
- 毕业十年了,自从做了开发用了很多软件,但距离写开发工具还很远
-
办公系统类:办公软件Word、Excel、PowerPoint三大必备技能+腾讯/金山在线文档解压缩操作:7-zip/winrar文件文本处理:Notepad++(文本编辑器正则表达式超级好...
- 盘点Java中最没用的知识⑤:这3个老古董你还在代码里“考古”?
-
一、Stack类:“继承Vector”的历史bug,为何成了性能拖油瓶?你是不是在学Java集合时,老师说过“栈结构用Stack类”?是不是在老代码里见过"newStack<>(...
- Gemini 2.5 Pro 0506发布,编程最强大模型, 碾压 Claude3.7 sonnent
-
一、Gemini2.5Pro(I/Oedition)发布1、为何叫I/Oedition?谷歌史上最强编程模型Gemini2.5Pro(I/Oedition)发布,具体型号是Gemin...
- 如何让无聊变得有趣(附本人大量美图)
-
文/图:金冬成在这条长300公里的公路上,我已经来回往返了无数次。3小时车程,一个人,想想都是多么无聊的一件事。其实,人生道路上,类似这种无聊的事情有很多很多。无聊的事情、枯燥的工作,往往让我们容易失...
- Oracle 推出 Java 24,增强 AI 支持和后量子加密
-
导读:Oracle宣布正式发布Java24,该语言增加了几个新功能,例如StreamGatherersAPI和Class-FileAPI的可用性,以及专门为AI推理和量子安全设计...
- 公司ERP突然变慢?“索引重建”这颗“药”可不能随便吃!
-
各位老板、IT小哥、财务小姐姐,有没有遇到过公司ERP系统突然卡顿得像“老爷车”,点个按钮半天没反应,急得直跺脚?这时候,可能有人会跳出来说:“我知道,重建一下数据库索引就好了!”听起来像个“神操作”...
- 基于Java实现,支持在线发布API接口读取数据库,有哪些工具?
-
基于java实现,不需要编辑就能发布api接口的,有哪些工具、平台?还能一键发布、快速授权和开放提供给第三方请求调用接口的解决方案。架构方案设计:以下是一些基于Java实现的无需编辑或只需少量编辑...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 一文带您了解数据库的行列之争:行式与列式存储的异同
- NL2SQL(三)开源项目怎么选:talk is cheap, show me the code!
- JDK25长期支持版九月降临:18项王炸功能全解析
- OceanBase 推出单机版 高度兼容MySQL和Oracle
- 黄远邦:应对7月1日闰秒对Oracle数据库影响
- MySQL数据库密码忘记了,怎么办?(mysql 数据库密码)
- Chinese AI Talent in Spotlight as Nvidia and Meta Escalate Talent War
- 用Cursor开启JAVA+AI生涯(javascirpt怎么开启)
- 毕业十年了,自从做了开发用了很多软件,但距离写开发工具还很远
- 盘点Java中最没用的知识⑤:这3个老古董你还在代码里“考古”?
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)