Linux进程
一.进程关键概念
我们首先需要了解下面5个问题:
问题一:什么是程序?什么是进程?有什么区别?
问题二:如何查看系统中有哪些进程?
问题三:什么是进程标识符?
问题四:什么叫父进程,什么叫子进程?
问题五:c程序的存储空间是如何分配的?
1.什么是程序?什么是进程?有什么区别?
(1)程序就是静态的概念,也就是在磁盘中生成的文件。
(2)进程是程序的一次运行活动,通俗点来说就是程序跑起来,系统中就多了一个进程。 (动态)
2.如何查看系统中有哪些进程?
(1)ps指令查看 (配合grep)
例:ps -aux 查看全部进程
如果想要查看指定进程,我们就需要把其他进程过滤掉。 (配合grep)
ps -aux|grep init 查看init进程,过滤掉其他进程
(2)top指令查看,(类似windows的任务管理器)
例:在命令行输入 top 即可查看全部进程。
3.什么是进程标识符?
(1)每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。
在操作系统中,默认
Pid=0 : 称为交换进程(swapper)
作用: 进程调度
Pid=1 : init进程
作用 : 系统初始化
编程调用getpid函数获取自身的进程标识符
getppid获取父进程的进程标识符
demo1.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid; // 进程标识符
pid = getpid(); // 获取自身的进程
printf("my pid is %d\n",pid); //打印该进程的id
while(1);
return 0;
}
运行结果:
4.什么叫父进程,什么叫子进程?
进程A创建了进程B
则进程A叫做父进程,B叫做子进程。
5.c程序的存储空间是如何分配的?
1.正文(代码段):程序流程控制,算法之类的代码。
2.数据段:初始化过的变量。
函数外未被初始化的数据,叫做bss段。
3.堆:通常在堆中进行动态存储分配。
调用malloc相关的函数去申请空间,返回的内存地址都在堆空间里。
4.栈:函数调用以及函数的局部变量和产生的信息放到栈里。
5.命令行参数和环境变量:如 argc argv等
二.创建进程函数fork的使用
使用fork函数创建一个进程
pid_t fork(void);
fork函数调用成功,返回两次;
返回值为0, 代表当前进程是子进程
返回值为1, 代表当前进程是父进程。
若调用失败,返回-1.
demo_2_1.c
理解getpid函数和fork函数
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = getpid(); //获取自身(当前)的进程
fork(); //创建一个新的进程,fork()以后就有2个进程在跑.
printf("my pid is %d,current pro id:%d\n",pid,getpid());
//不同的两个进程输出两个不同的id;
//pid一样,但两个getpid()的结果不同
return 0;
}
输出结果:(仔细观察两次进程的不同)
demo_2_2.c
fork函数调用成功,返回两次;
返回值为0, 代表当前进程是子进程
返回值为非负数, 代表当前进程是父进程。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
printf("father id:%d\n",getpid());
//创建了一个新的进程,但原来的进程还会继续执行下去
//新进程的id赋值给了pid,所以此时子进程的 pid==0,所以两个都会打印。
pid = fork();
if(pid > 0)
{
printf("father progess id:%d\n",getpid());//father
}
else if(pid == 0)
{
printf("child progess id:%d\n",getpid());//child
}
//返回2个,所以if和else if 的语句都会被执行
return 0;
}
输出结果:
demo_2_3.c
查看不同进程的id,
当父进程创建了子进程之后 ,几乎是两个进程同时在跑
fork()
父进程的返回值是子进程的id;
子进程的返回值是0;
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid1;
pid_t pid2;
pid_t newpid; //定义一个新的id
pid1 = getpid();
printf("father id:%d\n",pid1);
newpid = fork();//开辟了子进程,相当于两个通道,父子进程各自在各自的进程跑,也就是各自在各自的通道执行下面的程序。
pid2 = getpid();
printf("after id:%d\n",pid2);
if(pid1 == pid2)
{
printf("father progess id:%d\n",getpid());//father
printf("newpid:%d\n",newpid);// newpid>0, 返回的是子进程的 id
}
else
{
printf("child progess id:%d\n newepid:%d\n",getpid(),newpid);//child
//
}
return 0;
}
输出结果:
三.fork编程实战
fork一般的应用场景: 服务器和客户端的通信
demo_3.c
简单模拟一下,了解fork的使用
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
int data;
while(1)
{
printf("please input a data:\n");
scanf("%d",&data);
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("Wait,do nothing!\n");
}
}
return 0;
}
四.fork和vfork创建进程+进程退出
1.fork创建子进程的一般目的
(1)一个父进程希望复制自己,使父子进程同时执行不同的代码段。(在网络服务进程中常见——— 父进程等待客户端的服务请求,当这种请求到达时,父进程调用fork,使子进程处理此请求,父进程则继续等待下一个服务请求到达。)
(2)一个进程要执行一个不同的程序,这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec.
2.由fork创建的新进程被称为子进程,fork被调用一次,但返回两次;
两次返回的唯一区别是子进程的返回值是0 (一个进程只会有一个父进程,所以子总是可以调用getppid以获得其父的进程ID)(进程ID: 0 总是由内核交换进程使用,所以一个子进程的进程ID不可能为0). 而父进程的返回值是新进程的ID,(一对多,但是没有一个函数使一个进程获得所有子进程的进程ID);
3. 子进程是父进程的副本.
例:子进程获得父进程数据空间,堆,栈的副本。(注意:这是子进程所拥有的副本,父子进程并不共享这些存储空间部分,父子进程共享正文段。)
4.vfork函数 也可以创建进程,与fork有什么区别?
关键区别一:
vfork直接使用父进程存储空间,不拷贝。
关键区别二:
vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
观察下面demo,了解vfork的用法;
demo_4.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0)
{
while(1)
{
printf("father pid:%d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("child pid:%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3)
{
exit(0); // 子进程退出的时候一定要保证是用较好的方式退出,要不然容易被破坏掉。
//子进程先运行,当子进程调用exit退出后,父进程才执行
}
}
}
return 0;
}
运行结果:
5.进程退出
(1)正常退出:
_1. main函数调用return退出
_2.进程调用exit(),标准c库退出
_3.进程调用_exit()或_Exit(), 属于系统调用退出
_4.进程最后一个线程返回
_5.最后一个线程调用pthread_exit 退出
(2)异常退出
_1.调用abort
_2.当进程收到某些信号时,如Ctrl+C
_3.最后一个线程对取消(cancellation),请求做出相应
五.父进程等待子进程退出
1.父进程等待子进程退出,并收集子进程的退出状态。
等待: wait函数
wait(状态码) :
子进程调用 exit()把状态码返回来,wait解析状态码(需要用到下面的宏)。
demo_5.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
int status=6;
pid = fork();
if(pid > 0)
{
wait(&status);// 等待子进程退出,接收返回的状态码
printf("Child quit,child stauts = %d\n",WEXITSTATUS(status));//打印 3 解析状态码
while(1)
{
printf("father pid:%d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("child pid:%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3)
{
exit(3); //退出,返回3
}
}
}
return 0;
}
2.子进程退出状态不被收集,变成僵死进程。
**僵死进程 **
参考上面demo_4.c, (僵死进程)(zombie)
————————————————————————————————
wait函数
3.wait和waitpid的区别
wait使调用者阻塞 ,waitpid有一个选型,可以使调用者不阻塞。
此处可以去百度查询waitpid的用法
4.孤儿进程
------父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
——Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
demo_6.c
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int status=10;
int cnt=0;
pid = fork();
if(pid > 0)
{
printf("this is father print, pid=%d\n",getpid());
}
else if(pid == 0)
{
while(1)
{
printf("this is child,pid=%d, my father pid=%d\n",getpid(),getppid()); //getppid() 获取父进程的pid
sleep(1);
cnt++;
if(cnt == 5)
{
exit(3);
}
}
}
return 0;
}
运行结果:
init进程收留孤儿进程,变成孤儿进程的父进程。
getppid() 获得的pid 为init进程的pid。
六.exec族函数
参考(精彩)博文:exec组函数
https://blog.csdn.net/u014530704/article/details/73848573
1.exec族函数的作用,定义以及函数原型
exec族函数的作用:
——我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
demo_6_1.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;
}
再写另外一个程序:
注意perror函数的用法:查看程序出错的原因
execl
//文件pro_6_2.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("./pro_6","pro_6","abc",NULL) == -1)
{
printf("execl failed!\n");
perror("why");//查看失败的原因,perror()
}
printf("after execl\n");
return 0;
}
结果:
2.whereis + 指令 :查询指令的目录
例:
—— whereis ls 查找ls的根目录
先了解上面参考博文的一些用法,之后再举例子简单了解下:
3.通过execl函数查询系统时间
首先在linux查询时间非常简单, 直接在终端输入:date
c语言实现:
(1)现在终端输入:whereis date
——————查看date的根目录
(2)通过c语言实现终端输入date指令
demo_7.c
//文件pro_6_3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("this is system date:\n");
if(execl("/bin/date","date",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why");//查看失败的原因,perror()
}
printf("after execl\n");
return 0;
}
-
-
- exec函数族:execl, execlp, execle, execv, execvp, execvpe
可通过查看参考博文了解。
- exec函数族:execl, execlp, execle, execv, execvp, execvpe
-
4.exec族函数配合fork使用
为什么要用exec族函数,有么用?
- 一个进程要执行一个不同的程序,这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec.
demo.c
实现功能:- 当父进程检测输入为1 时,创建子进程把配置文件的字段值修改掉。
//pro_7.c
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int data;
while(1)
{
printf("please input a data:\n");
scanf("%d",&data);
if(data == 1)
{
pid = fork();//创建一个子进程
if(pid > 0)
{
wait(NULL);
}
else if(pid == 0)
{
execlp("./changedata","changedata","config.txt",NULL);
// 在子进程中执行changedata
// 这块的changedata是通过 gcc changedata.c -o changedata 之后的可执行程序。
}
}
else
{
printf("wait,do nothing!\n");
}
}
return 0;
}
修改程序的配置文件 changedata:
//changedata.c
#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(int argc,char **argv)
{
int fdSrc;
char *readbuf = NULL;
if(argc != 2)
{
printf("params error\n");//params 参数
exit(-1);
}
fdSrc = open(argv[1],O_RDWR); //打开src文件
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); //读取src文件内容,存储到readbuf中 //char *strstr(const char *haystack, const char *needle);
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));//将readbuf里的内容写入到Des文件中
close(fdSrc);
return 0;
}
- 修改config.txt的内容
- 未执行程序前的config.txt
- 程序执行后
七.system函数
1.system原型
参考经典博文:system用法
demo_7.c
对exec组函数的demo进行修改, 修改配置 文件的内容
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int data;
while(1)
{
printf("please input a data:\n");
scanf("%d",&data);
if(data == 1)
{
pid = fork();//创建一个子进程
if(pid > 0)
{
wait(NULL);
}
else if(pid == 0)
{
// execlp("./changedata","changedata","config.txt",NULL);
system("./changedata config.txt");
//相当于在命令行直接输入 ./changedata config.txt
}
}
else
{
printf("wait,do nothing!\n");
}
}
return 0;
}
demo_7_2.c
- 查看系统时间, 类似于上面的execl对date的操作
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("this is system date:\n");
//if(execl("/bin/date","date",NULL,NULL) == -1)
if(system("date") == -1)
{
printf("execl failed!\n");
perror("why");//查看失败的原因,perror()
}
printf("after execl\n");
return 0;
}
注意: 还是要注意system与exec族函数的区别,
exec组函数 执行成功后,不会打印后面的 printf(“after execl\n”);
system函数 执行成功后,还是会打印后面的 printf(“after execl\n”);
八.popen函数
- 参考博文:linux下的popen使用心得
- https://blog.csdn.net/libinbin_1014/article/details/51490568
popen比system在应用中好处是:popen可以获取输出结果
#include<stdio.h>
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
int main()
{
FILE *fp;
FILE *fp2;
char ret[1024] = {0};
fp = popen("ps","r"); // 执行ps指令, 并且可以获取输出结果
int n_read = fread(ret,1,1024,fp);
printf("read ret:%d byte\ncontext:%s\n",n_read,ret);
//还可以把内容写入到ps.txt文件中
fp2 = fopen("./ps.txt","w+");
fwrite(ret,1,1024,fp2);
fclose(fp2);
return 0;
}
平时笔记,忘了就来这里翻- - - - - - - - - - -
笔记终于写完了,good!!!