linux进程和进程通信编程(1)
What makes the desert beautiful is that somewhere it hides a well.
沙漠之所以美丽,是因为在它的某个角落隐藏着一口井.
linux进程和进程通信编程(1)
- 1.什么是进程
- 2.进程id(pid)
- 3.进程间通信的方法
- 管道
- 信号
- IPC
- Socket
- 4.创建进程
- fork
- fork有三个返回值
- 父进程pid为1的原因
- 5.exec函数族
- execl
- 15.c代码
- 6.ps
- 7.kill
- 16.c代码
- 8.孤儿进程
- 17.c代码
- 9.僵尸进程
- 172.c代码
- 10.wait
- 18.c代码
- 11.守护进程
- 如何创建守护进程
- 19.c代码
1.什么是进程
正在运行的程序
进程 : 相当于一个同学
进程组 : 相当于一个班
会话 : 相当于一个年级
进程直接要通过linux内核进行通信
就绪态: 就差cpu
执行态到阻塞态: 因为某件事给卡住了
2.进程id(pid)
每个进程唯一的标识符
3.进程间通信的方法
管道
有名管道, 无名管道
信号
发送, 结束, 处理
IPC
共享内存, 消息队列, 信号灯
Socket
4.创建进程
用一个进程(父进程)去创建另一个进程(子进程)
子进程相当于父进程的拷贝
fork
参考程序14.c
//头文件
#include <sys/types.h>
#include <unistd.h>
fork有三个返回值
在父进程里面,返回子进程pid; 在子 进程里返回0, 出错返回负数
原本这整个程序是父进程, 最开始用了fork, 就生成了一个子进程(看不见的), 父子进程都去完整的执行了这个程序(因为可以看见分别执行了两次i++), 只是父进程只能去输出第一个if, 子进程只能去输出第二个if
父进程pid为1的原因
因为由于父进程先退出了,造成子进程被init(ID=1)接管,所以用getppid出来的是1. 最后在跑父进程的时候加了sleep就能保证父进程后退出。
5.exec函数族
execl
相关程序15.c
用于让父子进程去执行不同的程序(x.sh)或命令(ls)
execl执行之后, 子进程就不会再往后执行了
execl("要执行的文件的绝对路径", "./要执行的文件", 要执行的文件的参数);
1 . 用法
查找要执行的文件的绝对路径, 记得给需要执行的文件加权限
execl("/root/amiao/15.1.sh", "./15.1.sh", NULL);
如果执行成功, j就不返回, 不成功 , j就返回-1
2 . execl执行之后, 子进程就不会再往后执行了
比如这个, 父进程把整个程序执行完了(因为输出了i), 子进程执行了execl之后就没有执行i++了
这个也可以看出来, 把execl移到前面去, a++也不执行了
3 . 放父进程也一样, 不执行a++了
4 . 执行pwd命令
一般系统命令路径就是/bin/xx
15.c代码
// execl
#include <stdio.h> //用于main函数
#include <stdlib.h>
#include <sys/types.h> //用于pid_t
#include <unistd.h> //用于close,read,write,fork
int main() //不含参数
{
int j;
int a = 0;
int b = 0;
pid_t pid; //定义一个pid来接收fork的返回值
pid = fork();
int i = 0;
if (pid < 0)
{
printf("创建失败");
}
//父进程
if (pid > 0)
{
printf("这是父进程,其pid为%d\n", getpid()); // getpid是获得当前进程id
}
//子进程
if (pid == 0)
{
printf("a:%d\n", a);
printf("这是子进程,其pid为%d,", getpid()); // getpid是获得当前进程pid
printf("其父进程pid为%d\n", getppid()); // getppid是获得当前进程父进程的pid
// j = execl("/root/amiao/15.1.sh", "./15.1.sh", NULL); //运行15.1.sh,要用绝对路径
// printf("j:%d\n", j);
execl("/bin/pwd", "pwd", NULL);
printf("b:%d\n", b);
}
i++;
printf("i:%d\n", i);
}
6.ps
用于列出系统中正在运行的进程的各种信息, 比如pid
查看某一个命令的进程
7.kill
用于杀死进程
相关程序16.c
要一边运行16.c(编译成hell), 一边查pid才行, 不然pid查的是错的
如果是按f9运行的(这里我用的VScode远程连接的kali), 就直接查16(VS把16.c编译成16)
16.c代码
// kill的用法
#include <stdio.h> //用于main函数
#include <stdlib.h>
#include <sys/types.h> //用于pid_t
#include <unistd.h> //用于close,read,write,fork
int main() //不含参数
{
pid_t pid; //定义一个pid来接收fork的返回值
pid = fork();
if (pid < 0)
{
printf("创建失败");
}
//子进程
if (pid == 0)
{
while (1)
{
printf("这是子进程,其pid为%d,", getpid()); // getpid是获得当前进程pid
}
}
}
8.孤儿进程
参考进程17.c
就是一个没结束的子进程, 但它的父进程已经结束了
这个子进程的ppid为1(有些特殊的系统可能不是1)
比如这个,先运行完父进程,再运行子进程,就得到父进程为1
17.c代码
// 孤儿进程
#include <stdio.h> //用于main函数
#include <stdlib.h>
#include <sys/types.h> //用于pid_t
#include <unistd.h> //用于close,read,write,fork
int main() //不含参数
{
pid_t pid; //定义一个pid来接收fork的返回值
pid = fork();
if (pid < 0)
{
printf("创建失败");
}
//父进程
if (pid > 0)
{
while (1);
printf("这是父进程,其pid为%d\n", getpid()); // getpid是获得当前进程id
}
//子进程
if (pid == 0)
{
// sleep(2); //停2s,让父进程先结束,再运行子进程
printf("这是子进程,其pid为%d,", getpid()); // getpid是获得当前进程pid
printf("其父进程pid为%d\n", getppid()); // getppid是获得当前进程父进程的pid
}
}
9.僵尸进程
参考进程172.c
是一个没有被释放进程块的子进程
父进程没结束, 子进程结束了, 但是父进程不去释放这个进程控制块
kill父进程
172.c代码
// 僵尸进程
#include <stdio.h> //用于main函数
#include <stdlib.h>
#include <sys/types.h> //用于pid_t
#include <unistd.h> //用于close,read,write,fork
int main() //不含参数
{
pid_t pid; //定义一个pid来接收fork的返回值
pid = fork();
if (pid < 0)
{
printf("创建失败");
}
//父进程
if (pid > 0)
{
while (1);
}
//子进程
if (pid == 0)
{
// sleep(2); //停2s,让父进程先结束,再运行子进程
printf("这是子进程,其pid为%d,", getpid()); // getpid是获得当前进程pid
printf("其父进程pid为%d\n", getppid()); // getppid是获得当前进程父进程的pid
}
}
10.wait
用于减少僵尸进程产生
当父进程调用了wait, 就会直接阻塞父进程, 然后去把父进程的子进程结束之后的空间给释放了
参考程序18.c
//头文件
#include <wait.h>
18.c代码
//wait,用于减少僵尸进程产生,当父进程调用了wait, 就会直接阻塞父进程, 然后去把父进程的子进程结束之后的空间给释放了
#include <stdio.h> //用于main函数
#include <stdlib.h>
#include <sys/types.h> //用于pid_t
#include <unistd.h> //用于close,read,write,fork
#include <wait.h> //用于wait
int
main() //不含参数
{
pid_t pid; //定义一个pid来接收fork的返回值
pid = fork();
if (pid < 0)
{
printf("创建失败");
}
//父进程
if (pid > 0)
{
sleep(2); //停2s,让子进程先结束,再运行父进程
int status;
wait(&status);
if (WIFEXITED(status) == 1) // WIFEXITED(status)==1表示子进程是正常运行完退出的,不是被kill的
{
printf("子进程结束的返回:%d\n" ,WEXITSTATUS(status));//返回子进程中的exit里面的数
}
}
//子进程
if (pid == 0)
{
printf("这是子进程,其pid为%d,", getpid()); // getpid是获得当前进程pid
printf("其父进程pid为%d\n", getppid()); // getppid是获得当前进程父进程的pid
exit(6);
}
}
11.守护进程
就是后台进程, 与所有终端没有关联, 不能和用户交互, 不能ctrl+c退出
参考程序19.c
如何创建守护进程
其中1 ,2 ,6 是必须的
1 . 必须是init(pid=1的那个) 的子进程
用fork创建一个子进程, 然后让父进程用exit退出, 然后这个子进程就是init的子进程了
2 . 不跟终端交互
用setsid函数创建一个新会话, 会话里面的首进程不和终端交互
3 . 用chdir , 将当前目录改成根目录
因为有时候会把程序写在U盘里, U盘拔了 , 就不能在系统里面运行了, chdir用于避免这种情况
4 . 重设umask文件掩码
父进程和子进程掩码是一样的, 如果不想要一样的, 就用umask改了
5 . 关闭文件描述符
用于节约资源
6 . 执行代码
该输出的还是会输出,只是重启时会开机自启
19.c代码
// 守护进程
#include <stdio.h> //用于main函数
#include <stdlib.h>
#include <sys/types.h> //用于pid_t
#include <sys/stat.h>
#include <unistd.h> //用于close,read,write,fork
int main() //不含参数
{
pid_t pid; //定义一个pid来接收fork的返回值
pid = fork();
if (pid < 0)
{
printf("创建失败");
}
//父进程
if (pid > 0)
{
exit(0); //让父进程直接退了
}
//子进程
if (pid == 0)
{
setsid(); //用setsid函数创建一个新会话,不跟终端交互
chdir("/"); //将当前目录改成根目录
umask(0); //重设umask文件掩码
close(0); //关闭文件描述符
while (1) //用while循环写出一直要执行的代码
{
printf("hh");
printf("其父进程pid为%d\n", getppid());
}
}
}