经典五问:
1.什么是程序?什么是进程?
从是否运行进行判断:
gcc xxx -o pro,磁盘中生成的pro文件,就是程序
进程是程序一次运行活动
程序是静态的概念,进程是动态的概念。
2.如何查看系统中的进程:
在linux中
a.使用ps(-aux) 命令查看,使用 grep命令过滤
例如: ps -aux | grep init
b. top 指令,类似window的任务管理器
3.什么是进程标识符:
每一个进程 都有一个非负整数 表示唯一ID,叫 pid
pid=0,称为交换进程(swapper),作用--进程调度
pid=1,init进程,作用 -- 系统初始化
调用getpid() 函数获取自身的进程id
getppid() -- 获取父进程id
#include <sys/types.h>
#include <unistd.h>int main()
{
pid_t pid;
pid = getpid();
printf("pid == %d\n",pid);
while(1);
return 0;
}
4. 什么叫父进程?什么叫子进程?
if 进程A创建了进程B,那么A是B 的父进程,B是A的子进程。
5.C程序存储空间是如何分配?
高地址 ------------------------> 低地址
命令行参数和环境变量-----> 栈(函数里的形参 和 局部变量) -------> 堆(malloc等动态内存函数申请的内存空间) ------------>未初始化的数据(BSS段 int a;) -------->初始化的数据(数据段 int b=10;)----->正文(代码段)
int a = 0; //全局初始化区
char *p1; //全局未初始化区
void main()
{
int b; //栈
char s[] = “abc“;//栈
char *p2; //栈
char *p3 = “123456“; //123456\0在常量区,p3在栈上;体会与 char s[]="abc"; 的不同
static int c =0; //全局初始化区
p2 = (char *)malloc(20); //堆区
strcpy(p1, “123456“); //123456\0在常量区,编译器可能将它与p3指向的 “123456 “优化成一块
}参考自:什么变量存放在栈和堆_什么样的数据进堆 什么样的数据进栈-CSDN博客
===================================================
fork函数
进程函数 fork 使用:
头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t fork(void);
返回值:
调用成功,调用一次,返回两次:
0--代表当前进程是子进程
非负数 -- 代表是父进程
调用失败,放回-1
--------------------------------------
fork_case:
case1 : 证明fork() 之后的语句,父子进程都会执行,fork之前的语句只有父进程执行
#include <sys/types.h>
#include <unistd.h>int main()
{
pid_t pid1;
pid_t pid2;
pid1 = getpid();
printf("Before fork,pid=%d\n",pid1);
fork(); //创建一个进程
pid2 = getpid();
printf("After fork,pid=%d\n",pid2);
if(pid1 == getpid()){
printf("This is father print. fatherPid=%d\n",pid1);
}
else {
printf("This is child print. childPid=%d\n",pid2);
}return 0;
}
--------------------------------
case2:验证: fork 返回值,fork调用一次,返回两次,>0 父进程, ==0子进程
#include <sys/types.h>
#include <unistd.h>int main()
{
pid_t pid;
pid=getpid();
printf("father pid =%d\n",pid);
pid=fork();
if(pid>0){//父进程
printf("This is father print,pid=%d\n",getpid());}
else if(pid == 0){ //子进程
printf("This is child print,pid=%d\n",getpid());}
return 0;
}
----------------------------
case3: 探索fork 父进程返回大于0的数有什么意义
等于子进程Pid号,子进程返回值就是0
//why给返回0的;理由:pid=0被交换进程所占用,不可能作为他的pid
#include <sys/types.h>
#include <unistd.h>int main()
{
pid_t pid;
pid_t pid2;
pid_t retpid;
pid=getpid();
printf("father pid =%d\n",pid);
retpid=fork();
pid2=getpid();
if(pid==pid2){//父进程
printf("This is father print,retpid=%d, pid=%d\n",retpid,pid2);}
else { //子进程
printf("This is child print,retpid=%d, pid=%d\n",retpid,pid2);}
return 0;
}
=============================================================
fork创建进程发生了什么?
进程早期设计的时候把,全拷贝--内存-空间所有内容都进行了拷贝
后面写时拷贝(不变的内容放在共享空间,不拷贝)只对copy on write- COW- 子进程 修改的内存进行单独拷贝
执行 fork 以后: fork之后的代码会被直接拷贝下来,给父子进程调度使用,
父子进程的变量独立,子进程改变自己的变量,父进程不受影响(因为子进程实际拷贝了一份单独的内存空间,和父进程独立)
case:父子进程内存空间独立
#include <sys/types.h>
#include <unistd.h>int main()
{
pid_t pid;
pid_t pid2;
int a=88;
pid=getpid();
printf("father pid =%d\n",pid);
fork();
pid2=getpid();
if(pid==pid2){//父进程
printf("This is father print, pid=%d\n",pid2);}
else { //子进程
printf("This is child print, pid=%d\n",pid2);
a+=12;
}
printf("a=%d\n",a);return 0;
}
============================
fork 创建子进程的目的:
1)父进程希望复制自己,使父子进程同时执行不同的代码段。 -- 常见于网络服务
2)一个进程要执行一个不同的程序。这在shell中常见,这种情况下,子进程从fork返回后,立即调用exec()
case:模拟网络服务(1)
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{
pid_t pid;
pid_t pid2;
int data = 0;
while (1)
{
printf("please input a data\n");
scanf("%d", &data);
if (data == 1)
{
pid = fork(); // 每次近来创建一个子进程if (pid > 0)
{ // 父进程
}
else
{ // 子进程
while (1)
{
printf("net request,pid=%d\n", getpid());
sleep(10);
}
}
}else
{
puts("waitting,do noting");
}
}return 0;
}
==========================================
vfork()函数
vfork 和fork的区别:
1.vfork是直接使用父进程的存储空间,不拷贝
2.vfork保证子进程先运行,当子进程调用exit()后,父进程才执行
case 区别验证:
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
pid = vfork();
if (pid > 0)
{
while (1)
{
printf("cnt = %d\n",cnt);
printf("this is father pid=%d\n", getpid());
sleep(1);
}
}
else
{
while (1)
{
printf("this is child pid=%d\n", getpid());
sleep(1);
cnt++;
if(cnt==5){
exit(0);
}
}
}return 0;
}
=======================================
进程退出:
正常退出:
1.Main 函数return
2.进行调用exit(),标准C库
3.进程调用_exit() 或者_Exit(),属于系统调用补充:(一个进程包含多个线程,当最后一个线程结束的时候进程就退出了)
1.进程最后一个线程放回
2.最后一个线程调用 pthread_exit
异常退出:
1.调用 abort
2.当进程接收到接收信号,如 ctrl+c
3.最后一个线程对取消(cancellation)请求做出响应
//无论进程如何退出最后都会执行内核的同一段代码,这段代码为所有相关进程关闭所有打开描述符,释放他的所有存储器
子进程调用exit _exit _Exit 的时候父进程可以调用waitpid 查看子进程退出的状态
推荐
exit() 是对_exit() _Exit()的封装,先处理缓冲区,再退出
=====================================
等待子进程退出:wait()
收集退出状态:
why?
创建子进程目的:
子进程退出状态if不被收集会变成僵尸进程
通过ps可以发现他的状态Z+ --僵尸进程,父进程--S+运行中
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
status参数:
是一个整形数值指针
非空: 子进程退出状态 放在他所指向的地址中
空: 不关心退出状态
检测wait和waitpid所放回终止状态的宏:
WEXITSTATUS(status); -- 正常退出
WIFSIGNALED(status) --异常退出
WIFSTOPPED(status) -- 暂停子进程的返回状态
WIFCONTINUED(status) -- 暂停好继续 的子进程放回的状态
wait下的父进程:
- 如果所有子进程都还在进行,则阻塞
-一个子进程已经终止,正等待父进程获取其终止状态,则该子进程终止状态立刻返回
-如果父进程没有终止子进程,则出错返回
wait 和 waitpid 区别:
wait使调用者阻塞,waitpid有一个选项,可以使得调用者不阻塞
当option = WBOHANG 的时候 不阻塞
====================================
孤儿进程:
概念
父进程先于子进程退出,使得子进程变成孤儿进程
linux系统为了避免出现过多的孤儿进程,init进程来收留孤儿进程,init进程就是孤儿进程的父进程
验证程序:
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
int status=10;
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\t my father ppid=%d\n", getpid(),getppid());
sleep(1);
cnt++;
if(cnt==3){
exit(3);
}
}
}return 0;
}
=====================================================
exec族函数:
但一个进程跑到一半的时候,调用exec族函数去执行另一个程序.
exec函数族:
execl,execlp, execv,execvp ,execle ,execvpe(e结尾不常用)
返回值:
exec成功不会放回,失败设置error并返回 -1,然后从原程序调用点往下执行
参数说明:
path:可执行文件路径
arg:可执行程序所带参数,第一个参数是程序名,没有带路径且arg必须以NULL结束
file: 如果参数中包含/,则视为路径,否则就按PATH环境变量,在他所在目录中搜寻可执行文件
perror -- 打印出错误信息;
perror(why); why: 错误信息
execl例子:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{
printf("before execl\n");
if(execl("./echo","echo","abc",NULL)==-1){
puts("execl error!!!");
perror("why");
}
puts("after execl");
return 0;
}
#include<stdio.h>int main(int argc,char **argv)
{
int i;
for(i=0;i<argc;++i){
printf("argv[%d]=%s\n",i,argv[i]);}
return 0;
}
gcc echoarg.c -o echo
-------------------------------------------
execl("/bin/ls","ls","-l",NULL) -- 第一个参数直接写绝对路径,调用系统的命令
execl("/bin/date","date",NULL)==-1) -- 获取系统时间
execlp -- p 通过系统环境变量找到指令,不用写绝对路径了
such as: execlp("ps","ps",NULL,NULL)==-1)
v -- 使用指针char * [](字符串数组-二维数组)代替参数
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{
printf("before execl\n");
char *argv[]={"ps",NULL,NULL};
if(execvp("ps",argv)==-1){
puts("execl error!!!");}
puts("after execl");
return 0;
}
exec 配合 fork使用:
case1: 实现功能,当父进程检测到输入为1 的时候,创建子进程吧配置文件的字段修改掉
=============
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>int main()
{
pid_t pid;
pid_t pid2;
int data = 0;
while (1)
{
printf("please input a data\n");
scanf("%d", &data);
if (data == 1)
{
pid = fork(); // 每次近来创建一个子进程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)
{
puts("not found");
exit(-1);
}
p = p + strlen("LENG=");
*p = '5';
lseek(fdSrc,0,SEEK_SET);write(fdSrc,readBuf,strlen(readBuf));
close(fdSrc);}
}else
{
puts("waitting,do noting");
}
}return 0;
}
----------------------
execl进行优化:
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>int main()
{
pid_t pid;
pid_t pid2;
int data = 0;
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)
{ // 子进程
execl("./changedata","changedata",NULL);
}
}else
{
puts("waitting,do noting");
}
}return 0;
}
=================================
system进行优化:
#include <stdlib.h>
int system(const char *command);
system -- 封装后的exec
调用/bin/sh失败返回127 其他失败返回-1
与exec 的区别,执行完后还会回去执行原程序的代码
=============================
一句system调用就可以执行之前的整个可执行文件
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>int main()
{
system("./exf");// 前面生成的可执行文件直接调用即可
return 0;
}
=================================================
popen
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
可以获取内存的输出结果:
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>int main()
{
char ret[1024]={0};
FILE *fp;
fp=popen("ps","r");
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
int nread = fread(ret,1,1024,fp);
printf("nread=%d\nret = %s\n",nread,ret);
return 0;
}