目录
一、进程相关概念
①程序:静态
②进程:程序动态的跑起就叫进程
③如何查看系统中有哪些进程?
·使用ps指令,ps -aux显示全部进程,实际工作中使用“ps -aux|grep (需要查看的进程名)”
·使用top指令,类似于windows任务管理器
④进程标识符:每个进程都有一个非负整数表示的唯一ID,叫做pid,类似于身份证
pid=0:交换进程(swapper)作用:进程调度
pid=1:init进程 作用:系统初始化
编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符
编程实现:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = getpid();
printf("%d\n",pid);
return 0;
}
结果:3282
⑤子进程与父进程:进程A创建了进程B,A叫做父进程,B叫做子进程
⑥C程序的存储空间是如何分配的?
正文:主函数中的执行语句,if...else...等(代码段)
初始化的数据:自定义的已经赋值的变量,包括main函数之内和之外的(数据段)
未初始化的数据:未赋值的变量(bss段)
堆:通常在堆中进行动态存储分配,如malloc申请的空间
栈:比如函数调用,函数局部变量,函数调用产生的信息
二、创建进程
1、fork函数
pid_t fork(void);
fork函数调用成功,返回两次
返回值为0,代表当前进程是子进程
返回值为非负数,代表当前进程是父进程
调用失败,返回-1
例1、fork的用法
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
//先打印一下目前pid
printf("father:id=%d\n",getpid());
//创建进程
pid = fork();
if(pid>0)//非负整数则是父进程
{
printf("this is father,pid=%d\n",getpid());
}
else if(pid == 0)//0则是子进程
{
printf("this is child,pid=%d\n",getpid());
}
return 0;
}
运行结果:
father:id=3678
this is father,pid=3678
this is child,pid=3679
fork之后的代码,子进程与父进程都会执行(选择性)
fork创建子进程的一般目的:
①一个父进程希望复制自己,使父子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
例2、仿真网络服务,请求达到时,data=1,创建子进程进行do net request
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data\n");
scanf("%d",&data);//用户输入1模仿服务请求达到
if(data == 1)
{
pid = fork();//服务请求达到,创建子进程去处理
if(pid>0)
{
}
else if(pid == 0)//子进程
{
while(1){
printf("do net request,pid=%d\n",getpid());
sleep(3);
}
}
}
else//用户输入其他,则没有服务请求
{
printf("do nothing\n");
}
}
return 0;
}
2、vfork函数
与fork的区别:
- vfork直接使用父进程存储空间,不拷贝
- vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行
例3、使用vfork前后对比
fork():
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid>0)
{
while(1){
printf("this is father,pid=%d\n",getpid());
sleep(1);
}
}
else if(pid ==0)
{
while(1){
printf("this is child,pid=%d\n",getpid());
sleep(1);
}
}
return 0;
}
运行结果:
this is father,pid=4474
this is child,pid=4475
this is father,pid=4474
this is child,pid=4475
this is father,pid=4474
this is child,pid=4475
this is father,pid=4474
this is child,pid=4475
vfork():
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = vfork();
if(pid>0)
{
while(1){
printf("this is father,pid=%d\n",getpid());
sleep(1);
}
}
else if(pid ==0)
{
while(1){
printf("this is child,pid=%d\n",getpid());
sleep(1);
}
}
return 0;
}
运行结果:
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
三、进程退出
正常退出:
- main函数调用return
- 进程调用exit(),标准C库
- 进程调用_exit()或者_Exit(),属于系统调用
- 进程最后一个线程退出(一个进程可以包含多个线程)
- 最后一个线程调用pthread_exit
异常退出:
- 调用abort
- 当进程收到某些信号时,如ctrl+C
- 最后一个线程取消(cancellation)请求做出响应
父进程要等待子进程退出并收集子进程的退出状态,子进程退出状态不被收集会变成僵尸进程。
四、进程等待
1、wait函数
pid_t wait(int *status);
status:一个整型指针,非空:子进程退出状态放在它所指向的地址中;空:不关心退出状态
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;//子进程的退出状态
pid = fork();//创建子进程
if(pid>0)
{
wait(&status);//父进程等待子进程退出
printf("child quit,status:%d\n",WEXITSTATUS(status));//收集子进程的退出状态
while(1){
printf("cnt=%d\n",cnt);
printf("this is father,pid=%d\n",getpid());
sleep(1);
}
}
else if(pid ==0)
{
while(1){
printf("this is child,pid=%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 5){
exit(3);//子进程退出,退出状态为3
}
}
}
return 0;
}
2、waitpid函数
与wait函数区别:
wait使用者阻塞,子进程不结束,父进程一直阻塞。而waitpid有一个选项可以不阻塞
pid_t waitpid(pid_t pid,int *status,int options);
参数说明:
①pid
==-1:等待任一子进程
>0:等待其进程ID与pid相等的子进程
==0:等待其组ID等于其调用进程组ID的任一进程
<-1:等待其组ID等于pid绝对值的任一子进程
②status
③options
常
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; int cnt = 0; int status = 10; pid = fork(); if(pid>0) { waitpid(pid,&status,WNOHANG);//父子一起执行,但是子进程会成为僵尸进程 printf("child quit,status:%d\n",WEXITSTATUS(status)); while(1){ printf("cnt=%d\n",cnt); printf("this is father,pid=%d\n",getpid()); sleep(1); } } else if(pid ==0) { while(1){ printf("this is child,pid=%d\n",getpid()); sleep(1); cnt++; if(cnt == 5){ exit(3); } } } return 0; }
用WNOHANG,不阻塞
五、孤儿进程
1、定义
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid>0)
{
printf("this is father,pid=%d\n",getpid());
}
else if(pid ==0)
{
while(1){
printf("this is child,pid=%d,my father pid=%d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 5){
exit(3);
}
}
}
return 0;
}
运行结果:
this is father,pid=3429
this is child,pid=3430,my father pid=3429
this is child,pid=3430,my father pid=1
this is child,pid=3430,my father pid=1
this is child,pid=3430,my father pid=1
六、exec族函数的使用
1、作用
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
2、函数原型
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);参数说明:
path:可执行文件的路径
arg:可执行文件的参数,第一个参数为可执行文件的名字,arg必须以NULL结束
file:路径
execl函数
例:demo7中excel(demo8),先运行demo8.-o demo8
demo7.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("before excel\n");
if(execl("./demo8","demo8",NULL,NULL) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
demo8.c:
#include <stdio.h>
int main()
{
printf("this is excel!\n");
}
执行结果:
before excel
this is excel!
举一反三:调用date获取当前时间
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("before excel\n");
if(execl("/bin/date","date",NULL,NULL) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:
before excel
Thu Jun 22 14:11:19 CST 2023
execlp函数
与execl函数的不同:execl函数需要去查看可执行文件的绝对路径,而excelp函数直接写文件名即可,会自动查找文件所在位置
execv函数
与execl函数不同:此函数需要把文件名、参数等提前写成一个数组
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("before excel\n");
char *argv[] = {"date",NULL,NULL};
if(execv("/bin/date",argv) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
execvp函数
在execv的基础上,不用写路径,会自动查找路径
例:exec配合fork使用
具体要求:当父进程检测到输入为1时,创建子进程把配置文件的字段值修改掉
①先写一个替换字段的程序生成可执行文件changdata
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data:\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid>0){
wait(NULL);
}
if(pid == 0){
int fdSrc;
char *readbuf = NULL;
fdSrc = open("config.txt",O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET);
readbuf=(char *)malloc(sizeof(char)*size +8);
int n_read = read(fdSrc,readbuf,size);
char *p = strstr(readbuf,"LENG=");
if(p==NULL){
printf("not found\n");
exit(-1);
}
p=p+strlen("LENG=");
*p='5';
lseek(fdSrc,0,SEEK_SET);
int n_write = write(fdSrc,readbuf,strlen(readbuf));
close(fdSrc);
exit(0);
}
}
else{
printf("wait,do nothing\n");
}
}
}
执行:
gcc demo9.c -o changdata
②先一个程序使用execl调用这个可执行文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data:\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid>0){
wait(NULL);
}
if(pid == 0){
//此处即为子进程调用另一个可执行文件
execl("./changdata","changdata","config.txt",NULL);
exit(0);
}
}
else{
printf("wait,do nothing\n");
}
}
}
七、system函数的使用
1、函数原型
int system(const char *command);
参数说明:是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。
返回值:
成功-返回进程的状态值
不能执行sh-返回127
失败-返回-1
2、作用
与execl作用相同,只不过直接加一个命令即可,不需要后面参数什么的
八、popen函数
1、函数原型
FILE *popen(const char *command, const char *type);
参数说明:
command同system函数
type:读或写的一种
r:文件指针连接到command的标准输出
w:文件指针连接到command的标准输入
返回值:
如果调用成功,则返回一个读或打开文件的指针,如果失败,返回null,具体错误根据error判断
int pclose(FILE *system)
参数说明:popen返回的文件指针
返回值:如果调用失败,返回-1
2、比system的好处:可以获取运行的结果