Linxu系统编程——进程
1.进程相关概念
问1. 什么是程序,什么是进程,有什么区别?
-
程序是静态的概念,gcc xxx.c –o pro
-
磁盘中生成pro文件,叫做程序
-
进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程
问2. 如何查看系统中有哪些进程?
-
使用
ps
指令查看ps -aux
实际工作中,配合grep来查找程序中是否存在某一个进程
-
使用top指令查看,类似windows任务管理器
问3. 什么是进程标识符?
-
每个进程都有一个非负整数表示的唯一ID,
叫做pid,类似身份证
Pid=0: 称为交换进程(swapper)
作用—进程调度
Pid=1:init进程
作用—系统初始化
- 编程调用getpid函数获取自身的进程标识符
getppid获取父进程的进程标识符
问4. 什么叫父进程,什么叫子进程?
-
进程A创建了进程B
那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系
问5. C程序的存储空间是如何分配?
2.创建进程函数fork的使用
使用fork函数创建一个进程
pid_t fork(void);
fork函数调用成功,返回两次
返回值为0, 代表当前进程是子进程
返回值非负数,代表当前进程为父进程
调用失败,返回-1
demo2
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = getpid();
fork();
printf("my pid is %d\n",pid);
return 0;
}
demo3
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = getpid();
fork();
printf("my pid is %d,current pro is %d\n",pid,getpid());
return 0;
}
demo4
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = getpid();
fork();
if(pid == getpid()){
printf("this is father pid\n");
}else{
printf("this is child pid= %d\n",getpid());
}
return 0;
}
demo5
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
printf("father pid = %d\n",getpid());
pid = getpid();
printf("get pid = %d\n",pid);
fork();
if(pid>0) {
printf("this is father pid= %d\n",getpid());
}else if(pid == 0){
printf("this is child pid= %d\n",getpid());
}
return 0;
}
3.创建进程函数fork的使用补充
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid_t pid2;
pid_t retpid;
pid = getpid();
printf("before fork:pid = %d\n",pid);
retpid = fork();
pid2 = getpid();
printf("after fork : pid = %d\n",pid2);
if(pid == pid2){
printf("this is father pid:%d ,retpid=%d\n",getpid(),retpid);
}else{
printf("this is child pid= %d,retpid = %d\n",getpid(),retpid);
}
return 0;
}
4.进程创建发生了什么
当fork后;原来的linux会全拷贝;后期用的写时拷贝(copy-on-write),共享内存,进行只读拷贝,只有对数据修改时,才会在子进程的地址空间拷贝修改
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid_t pid2;
pid_t retpid;
pid = getpid();
int data = 10;
printf("before fork:pid = %d\n",pid);
retpid = fork();
pid2 = getpid();
if(pid == pid2){
printf("this is father pid:%d ,retpid=%d\n",getpid(),retpid);
}else{
printf("this is child pid= %d,retpid = %d\n",getpid(),retpid);
data += 100;
}
printf("data = %d\n",data);
return 0;
}
输出结果:
5.创建新进程的实际应用场景及fork总结
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int data = 0;
while(1){
printf("please inter data\n");
scanf("%d",&data);
if(data == 1){ //当输入1时,创建子进程
pid = fork();
if(pid >0){ //父进程什么都不做
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid()); //子进程打印进程ID
sleep(3);
}
}
}else{
printf("waiting\n");
}
}
printf("data = %d\n",data);
return 0;
}
输出结果:
6.vfork创建进程
fork函数使用现象:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = fork();
if(pid >0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
}
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
}
}
return 0;
}
输出结果:
两个进程相互争夺CP资源,
vfork函数使用现象:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid >0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
printf("%d\n",cnt);
}
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(0);// 0:退出状态 让子进程正常退出(保证子进程正常退出,防止破坏cnt)
}
}
}
return 0;
}
输出结果:
vfork函数使用的是父进程存储空间,不拷贝;而且保证子进程先运行,只有等子进程调用exit(0),正常退出(保证cnt值不被破坏);父进程才执行
7.进程退出
8.父进程等待子进程退出(一)
僵尸进程:
- 子进程退出状态不被收集,变成僵死进程(僵尸进程)
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid >0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
printf("%d\n",cnt);
}
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
结果:
- 父进程等待子进程退出并收集子进程的退出状态
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid >0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
printf("%d\n",cnt);
}
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
输出结果:
wait函数
使用wait函数,等待子进程运行完,对子进程退出状态收集,防止子进程出现僵尸进程
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid >0){
wait(NULL); //等待子进程运行完;NULL不关心返回值
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
printf("%d\n",cnt);
}
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
输出结果:
正常退出,获取返回值
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 6;
pid = fork();
if(pid >0){
wait(&status);
printf("child quit,child status:%d\n",WEXITSTATUS(status)); //WEXITSTATUS(status):若为正常终止子进程返回的状态,则 //为真。对于这种情况可执行WEXITSTATUS(status),取子进程传送 //给exit、_exit或Exit参数的低8位
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
printf("%d\n",cnt);
}
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(5);
}
}
}
return 0;
}
输出结果:
9.父进程等待子进程退出(二)
waitpid函数
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 6;
pid = fork();
if(pid >0){
// wait(&status);
waitpid(pid,&status,WNOHANG); //pid是子进程的,不阻塞 ;非阻塞等待,子进程也会成为僵尸进程
printf("child quit,child status:%d\n",WEXITSTATUS(status)); //WEXITSTATUS(status):若为正常终止子进程返回的状态,则 //为真。对于这种情况可执行WEXITSTATUS(status),取子进程传送 //给exit、_exit或Exit参数的低8位
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
printf("%d\n",cnt);
}
}else if(pid == 0){
while(1){
printf("this is child pid= %d\n",getpid());
sleep(3);
cnt++;
if(cnt == 3){
exit(5);
}
}
}
return 0;
}
运行结果:
孤儿进程
实现:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 6;
pid = fork();
if(pid >0){
printf("this is child pid= %d\n",getpid());
}else if(pid == 0){
while(1){
printf("this is child pid= %d,father pid = %d\n",getpid(),getppid()); //getppid()在子进程里面获取父进程id,init进程id为1
sleep(3);
cnt++;
if(cnt == 3){
exit(5);
}
}
}
return 0;
}
运行结果:
10.exec族函数
exec族函数讲解博文
exec族函数函数的作用:
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
exec族函数定义:
可以通过这个网站查询:linux函数查询
功能:
在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
函数族:
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
函数原型:
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., 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[]);
返回值:
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
————————————————
pwd:显示路径
添加环境变量:export PATH=$PATH:路径
//文件echoarg.c
#include <stdio.h>
int main(int argc,char *argv[])
{
int i = 0;
for(i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n",i,argv[i]);
}
return 0;
}
execl函数
失败案列:(原因:路径不对)
//文件execl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("./bin/echoarg","echoarg","abc",NULL) == -1) //第一个参数就是添加要执行程序名和路径,第二个参数为执行程序名,第三个为执行程序所代参 //数(无写NULL),第四个是要以NULLL结束
{
printf("execl failed!\n");
perror("why"); //打印运行失败的原因
}
printf("after execl\n");
return 0;
}
运行结果:
成功案列:
//文件execl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("./echoarg","echoarg","abc",NULL) == -1) //第一个参数就是添加要执行程序名和路径,第二个参数为执行程序名,第三个为执行程序所代参 //数(无写NULL),第四个是要以NULLL结束
{
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
运行结果:
失败和成功在于执行程序文件目录,运行成功后,不会执行之前未执行的代码
execlp函数
//文件execl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execlp(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execlp("ls","ls","-l",NULL) == -1) //第一个参数就是添加要执行程序名和路径,第二个参数为执行程序名,第三个为执行程序所代参 //数(>无写NULL),第四个是要以NULLL结束
{
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
whereis ls
查找ls目录
运行结果:
tips:execl 和 execlp 的区别在于有没有路径名,execl需要添加路径名,execlp直接在环境变量中查抄可执行文件名
execv函数
//文件execvp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execvp(const char *path, char *const argv[]);
int main(void)
{
printf("before execvp****\n");
char *argv[] = {"echoarg",NULL,NULL};
if(execv("./echoarg",argv) == -1)
{
printf("execvp failed!\n");
}
printf("after execvp*****\n");
return 0;
}
运行结果:
execvp函数
//文件execvp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execvp(const char *file, char *const argv[]);
int main(void)
{
printf("before execvp****\n");
char *argv[] = {"ls","-l",NULL};
if(execv("ls",argv) == -1)
{
printf("execvp failed!\n");
}
printf("after execvp*****\n");
return 0;
}
运行结果:
11.exec族函数配合fork使用
使用:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 0;
while(1){
printf("please inter data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
exit(0);
}else if(pid == 0){
int fdSrc;
char *readbuf = NULL;
fdSrc = open("./config.txt",O_RDWR); //打开txt文件
int fdSrcSize = lseek(fdSrc,0,SEEK_END); //获得文件大小
lseek(fdSrc,0,SEEK_SET); //光标定位到文件头
readbuf = (char *)malloc(sizeof(char)*fdSrcSize+8); //根据获得大小开辟一个缓冲区
int n_read = read(fdSrc,readbuf,fdSrcSize); //读取内容到缓冲区
char *p = strstr(readbuf,"LENG="); //找到第一次需要查找的内容头部
if( p == NULL){
printf("No 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);
printf("change success!\n");
exit(0);
}
}else{
printf("waiting\n");
}
}
return 0;
}
程序运行前config.txt文件内容:
程序运行后config.txt文件内容:
exec族函数配合fork函数使用:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 0;
while(1){
printf("please inter data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
exit(0);
}else if(pid == 0){
execl("./changeData","changeData","config.txt",NULL);
printf("change success!\n");
exit(0);
}
}else{
printf("waiting\n");
}
}
return 0;
}
程序运行后:
12.system函数
system()函数的返回值如下:
成功,则返回进程的状态值;
当sh不能执行时,返回127;
失败返回-1;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 0;
while(1){
printf("please inter data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
exit(0);
}else if(pid == 0){
// execl("./changeData","changeData","config.txt",NULL);
system("./changeData config.txt");
printf("change success!\n");
exit(0);
}
}else{
printf("waiting\n");
}
}
return 0;
}
程序运行结果:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("before execl\n");
if(system("ls") == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:
13.popen函数
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
比system在应用中的好处:
可以获取运行的输出结果
使用system没有返回值:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
FILE *fp;
char ret[1024] = {"0"};
fp = popen("ls","r");
int n_read = fread(ret,1,1024,fp);
printf("ret = %s\n",ret);
return 0;
运行结果:
使用popen函数:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char ret[1024] = {"0"};
FILE *fp;
fp = popen("ls","r");
int n_read = fread(ret,1,1024,fp);
printf("read ret = %d; ret = %s\n",n_read,ret);
return 0;
}
运行结果:
14.进程总结
进程概念:5问
进程创建fork使用,返回两次
创建进程做了什么事?新的进程回去拷贝(写时拷贝)
程序空间如何分配?(问5)
vfork和fork的区别
exec族函数的使用方法