1.1、什么是程序,什么是进程,有什么区别?
1.1.1 程序
程序是静态的概念,gcc xxx.c -o pro; 磁盘中生成pro文件,叫做程序;
1.1.2 进程
进程是程序的一次运动活动,通俗点意思是程序跑起来了,系统中就多了一个进程;
1.1.3 区别
程序是永存的;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
程序是静态的观念,进程是动态的观念;
进程具有并发性,而程序没有;
进程是竞争计算机资源的基本单位 ,程序不是。
1.2、如何查看系统中有哪些进程?
命令 ps -aux 列出所有进程;
命令 ps -aux|grep init 把含**init**的进程筛选出来
命令 **top**查看进程(类似于window任务管理器)
1.3、什么是进程标识符?
1.3.1 进程标识符
每个进程都有一个非负整数表示的唯一 ID,叫做 pid ,类似身份证;
Pid = 0:称为交换进程(swapper)
作用:进程调度 //由它来决定,当前某一时刻由谁来跑;
Pid = 1:init进程
作用:系统初始化 //刚开始就应该执行的程序
1.3.2 getpid /getppid 函数
pid_t getpid(void); //获取自身的进程标识符;
pid_t getppid(void); //获取父进程的标识符;
- 添加头文件
-
#include <sys/types.h> #include <unistd.h>
-
getpid
获取自身的进程标识符; -
getpid
获取父进程的标识符;
程序演示
//demo.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);
while(1); //定住
return 0;
}
// 编译 然后将输出输入到hello文件中
//gcc -o hello hello_world.c
// 执行
//./hello
// 显示内容:Hello World
- 再打开一个终端,输入命令
top
,可见该进程正在执行;
1.4、什么是父进程,什么叫子进程?
- 进程A创造了进程B (A --> B)
- 进程A叫做父进程,B叫做子进程;
- 父子进程是相对的概念没理解为人类中的父子关系;
1.5、C程序的存储空间如何分配?
- 代码段: if else 等逻辑语句;
- 数据段: 初始化的数据 int a = 0;
bss
段:未初始化的变量;- 栈:
calloc
申请内存地址; - 堆: 函数地址,以及函数中所产生的局部变量;
2、Linux系统编程–创建进程
2.1 fork()
pid_t fork(void);
- 创造一个子进程;
- fork 调用成功(返回2下)
- 给父进程返回 非负数 且 正好为子进程的 ID 号;
- 给子进程返回 0;
调用失败:返回 -1;
- 添加头文件
#include <sys/tupes.h>
#include <unistd.h>
程序演示
//demo4.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("father:id=%d\n",getpid());
pid = fork(); //创造一个子进程
if(pid > 0)
{
printf("this is father print,pid = %d\n\n",getpid());
}
else if(pid == 0)
{
printf("this is child print pid =%d\n",getpid());
}
return 0;
}
2.2 进程创建发生了什么事
子进程不改变 变量a,共享;
子进程改变 变量a,从父进程里面拷贝一份变量a的地址给子进程;
2.3 fork创建一个子进程的一般目的
一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这是请求到达时,父进程调用**fork**,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
//一个进程要执行一个不同的程序。这对 shell 是很常见的情况。在这种情况下,子进程从fork返回后立即调用exec;
//demo5.c
#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);
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;
}
- 父进程一直检测客服端用户输入,每当用户输入”1“,创建一个子进程,每个子进程都不断输出自己的 ID 号;
- 父进程和子进程互不影响;
2.5 vfork()
vfork( )
与fork( )
的区别vfork
直接使用父进程存储空间,不拷贝。vfork
保证子进程先运行,当子进程调用 exit 推出后,父进程才执行。
//demo6.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("this is father print,pid = %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child print,pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt ==3)
{
exit(0); //退出 0 代表这个子进程退出的状态
//_exit(0);
//_Exit(0);
//不可以用 break 推出,会使cnt混乱
}
}
}
return 0;
}
3、Linux系统编程–进程退出
3.1 正常退出
Main函数调用return;
进程调用**exit()**,标准c库;
进程调用**_exit()**或者 _Exit(), 属于系统调用;
补充
进程最后一个线程返回了;
最后一个线程调用 pthread_exit ;
3.2 异常退出
调用 abort;
当进程收到某些信号时,如 ctrl + C;
最后一个线程对取消**(cancellation)**请求做出响应;
#include <stdio.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Eixt(int status);
4、Linux系统编程–父进程等待子进程退出
- 为什么等待,要干活
4.1 wait() /waitpid()
僵尸进程:**子进程退出状态不被收集,变成僵死进程
孤儿进程 :父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程;
-
Linux避免系统存在过多的孤儿进程,
init
进程收留孤儿进程,变成孤儿进程的父进程; -
进程状态:
- 命令:
ps -aux|grep a.out
S+ 正在运行;
Z+ 僵尸进程 (zombie)
- 命令:
-
4.1.1 检查
wait
和waitpid
所返回的终止状态的宏-解析退出码 - 解析status 退出码,也就是exit(?) 里的参数;
int status;
//子进程
exit(3);//退出码 ? = 3
//父进程
wait(&status); //里面的退出码给 *status;
WEXITSTATUS(status) //== 3 //返回"exit status"//解析退出码
- 父进程等待子进程退出,并收集子进程的退出状态;
4.1.2 wait
函数和 waitpid
函数介绍
- 将exit( ? ),里面的退出码给 * status;
- 如果其所有子进程都还在运行,则阻塞;
- 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
- 如果没有任何子进程,则立即出错返回;
status参数:(是一个整型数指针)
exit(3) status = 3;
非空:子进程退出状态放在它所指向的地址中;
空: 不关心退出状态;
wait 和waitpid的区别
wait使调用者阻塞;
waitpid 有一个选项,可以使调用者不阻塞;
waitpid 等待一个指定的子进程,而wait 等待所有的子进程,返回任一终止子进程的状态;
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork(); //创建子进程
if(pid > 0)
{
wait(&status);//等待子进程结束,防止子进程变成僵尸进程;
printf("child quit,child status = %d\n",WEXITSTATUS(status));//返回的终止状态的宏WEXITSTATUS(*status)
while(1)
{
printf("this is father print,pid = %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child print,pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt ==3)
{
exit(3); //退出
//_exit(3);
//_Exit(3);
}
}
}
return 0;
}
4.1.3 waitpid
//用的不多
pid_t waitpid(pid_t pid, int *status, int options);
pid 参数
pid == -1 : 等待任一子进程。就这一方面而言,waitpid 与 wait 等效;
pid > 0 : 等待其进程 ID 与 pid 相等的子进程;
pid == 0 :等待其组 ID 等于调用进程组ID的任一子进程;
pid < -1 : 等待其组 ID 等于pid 绝对值的任一子进程;
4.2 孤儿进程
-
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程;
-
- Linux避免系统存在过多的孤儿进程,
init
进程收留孤儿进程,变成孤儿进程的父进程
- Linux避免系统存在过多的孤儿进程,
//demo8.c 演示出孤儿进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();//创建子进程;
if(pid > 0)//父进程执行
{
printf("this is father print,pid = %d\n",getpid()); //打印一次,父进程就死了,死的比子进程早
}
else if(pid == 0)//子进程执行
{
while(1)
{
printf("this is child print,pid = %d, my father pid = %d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt ==3)
{
exit(3); //退出
//
}
}
}
return 0;
}
- 结果
- 父进程打印一次
- 子进程打印一次
- 父进程死了,子进程被
init
进程(896) 收留,成为了init
进程(896) 的子进程
5、Linux系统编程–exec族函数
(execl, execlp, execle, execv, execvp, execvpe)
推荐 blog:https://blog.csdn.net/u014530704/article/details/73848573
exec族函数函数的作用:
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
功能:
在调用进程内部执行一个可执行文件。既可以是二进制文件,也可以是任何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[]);
//结尾带e的不常用
返回值:
* exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
// e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
5.1 常用命令和函数
命令 whereis ls : 去找 ls 的绝对路径
打印输出函数 :perror(" ? why ?") : 类似于 printf() ;
perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。