Liunx C 语言 - 进程
一 . 进程相关概念
1. 什么是程序?什么是进程?俩者的区别?
(1)程序是静态的概念,程序是指令的集合;进程是动态的概念,是程序在处理机上的一次执行的过程。
(2)进程是暂时的,是有生命周期的。
2. 如何查看进程?
a. ps 指令
ps -aux | grep 过滤条件 (ps -aux|grep init)
b. top 指令
类似于 windows 的 任务管理器
top
3. 什么是进程标识符?
进程标识符就是一个非负唯一的整数 。
c 语言中可以使用 getpid() 函数获取进程标识符。
4. 什么是父子进程?
父子进程是指一个进程可以创建其他进程,被创建的进程就成为原始进程的子进程。 在这种模型中,父进程和子进程之间存在一种层次结构,父进程是子进程的直接控制者和管理者。子进程在创建时自动复制了父进程的内存空间、数据等资源,在创建过程中修改的子进程不会对父进程有任何影响。
5. C程序的存储空间如何分配 ?
- 代码段:存储程序的指令,通常是只读内存区域。在程序启动时被加载到内存,并且在整个程序运行期间不会改变。
- 数据段:存储程序静态分配的全局变量和静态变量,它们在程序整个运行期间都存在,以及一些字符串常量等。它也是只读内存区域。
- 堆区:由程序员手动管理的动态内存分配空间,通过 malloc() 和 free() 等函数进行内存的申请和释放。因此它是可变的,当需要时可以动态地向系统申请更多的内存,反之亦然。
- 栈区:用于存储函数调用过程中的临时变量、参数以及返回地址等,具有“先进后出”的特点。每次函数调用时,系统会自动为其分配一块栈空间,在函数返回时释放。也就是说,它是由系统自动管理的,不能手动修改大小。
二. 进程的创建
1. 相关函数
头文件:
#include <unistd.h>
函数:
pid_t fork(void);
描述:
通过复制调用进程来创建一个新进程。 新进程(称为子进程)是调用的精确副本,
子进程拷贝父进程的数据段,代码段.父子进程的执行次序不确定.
----------------------------------------
头文件:
#include <sys/types.h>
#include <unistd.h>
函数:
pid_t vfork(void);
描述:
创建一个进程,vfork() 子进程与父进程共享数据段, vfork 保证子进程先运行,
在调用exec 或exit 之前与父进程数据是共享的,在它调用exec或exit 之后父进程才可能被调度运行。
2. 使用案例
2-1. 模拟消息接入(fork)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
pid_t retid ;
int number = 10 ;
int data = 0 ;
while(1){
printf("plase scaner 1 to they \r\n");
scanf("%d",&data);
if(data == 1 ){
data = 0 ;
retid = fork();
if(retid > 0){
// 用于证明数据独立
number += getpid();
while(1){
// 证明创建了不同的子进程
printf("this child pid is %d ,data is %d \r\n",getpid(),number);
sleep(3);
}
}else if(retid == 0 ){
printf("this is faher\r\n");
}
}else{
printf("do nothing \r\n");
sleep(3);
}
}
return 0 ;
}
2-2. vfork
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
pid_t pid ;
pid = vfork();
// 子进程执行完后,才会执行父进程
if(pid == 0 ){
int i = 0 ;
for(;i < 5 ; i++){
printf("this is child -- %d \r\n",i);
sleep(1);
}
exit(0);
}else if(pid > 0 ){
int i = 0 ;
for(;i<20;i++){
printf("this is father \r\n");
sleep(1);
}
}
return 0 ;
}
3. 使用场景
2-1. 服务器对于多任务处理
2-2. 比如脚本编写
三. 进程的退出
1.相关函数
头文件:
#include <stdlib.h>
函数:
void exit(int status);
描述:
exit() 函数导致正常进程终止,并将status&0377的值返回给父进程(参见wait(2))。
-------------------------------------------------------------
头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
描述:
父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个
子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会
收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进
程,wait就会一直阻塞在这里,直到有一个出现为止。
2. 相关概念
a.正常退出
-
main函数调用return
-
进程调用exit(),标准C库
-
进程调用_exit()或者_Exit(),系统调用
-
进程最后一个线程返回
-
最后一个线程调用pthread_exit
b.异常退出
-
调用abort
-
当进程收到某些信号时,如ctrl+c
-
最后一个线程对取消(cancellation)请求作出响应不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
c. 父进程收集子进程退出状态
父进程等待子进程退出并收集子进程退出状态
子进程退出状态不被收集,会变成僵尸进程
3.使用案例
a. 父进程等待子进程退出(收集状态)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
pid_t pid;
int status = 10 ;
pid = fork();
if(pid == 0 ){ // child process
int i = 0 ;
for( ; i< 5 ; i++){
printf("this child my pid is %d , myfather pid is %d \r\n",getpid(),pid);
sleep(1);
}
}else if(pid > 0 ){ // father process
printf("等待子进程退出\r\n");
wait(&status);
printf("子进程退出,状态码为 :%d\r\n",WEXITSTATUS(status));
while(1){
printf("this is father process my pid is %d \r\n",getpid());
sleep(2);
}
}
return 0 ;
}
b.僵尸进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
pid_t pid;
pid = fork();
if(pid == 0 ){ // child process
int i = 0 ;
for(;i < 5;i++){
printf("this child my pid is %d , myfather pid is %d \r\n",getpid(),pid);
sleep(3);
}
exit(-1);
}else if(pid > 0 ){ // father process
while(1){
printf("this is father process my pid is %d \r\n",getpid());
sleep(2);
}
}
return 0 ;
}
c. 父进程优先于子进程退出(孤儿进程)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
pid_t pid;
pid = fork();
if(pid == 0 ){ // child process
int i = 0 ;
for(;i < 5;i++){
printf("this child my pid is %d , myfather pid is %d \r\n",getpid(),getppid());
sleep(3);
}
exit(-1);
}else if(pid > 0 ){ // father process
while(1){
printf("this is father process my pid is %d \r\n",getpid());
sleep(2);
}
}
return 0 ;
}
四. 其他程序的调用 (exec族函数,system,popen)
详细介绍可以查看 : https://blog.csdn.net/u014530704/article/details/73848573
exec 函数
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
//int execl(const char *path, const char *arg, .../* (char *) NULL */);
/* path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
*/
printf("before execl\n");
if(execl("/bin/ls","ls",NULL,NULL) == -1)//通过whereis指令找到ls命令的位置
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
int main(){
//int execlp(const char *file, const char *arg, .../* (char *) NULL */);
printf("before execl\n");
if(execlp("ls","ls","-l",NULL) == -1)//不用找到命令的路径
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
可以通过 whereis ls 查找指令位置 , echo $PATH 查看环境变量,echo PATH=$PATH:/bin/data 添加data 指令
system (是对 exec 函数的封装,可执行后面的操作)
本质上是对execl函数的二次封装,可查看其源码。实际上比execl更好用
和execl区别:system执行完该函数后,还会继续执行后面的函数 ,而exec则不会
/*
#include <stdlib.h>
int system(const char *command);
system()函数返回值如下:
成功,则返回进程的状态值
当sh不执行时,返回127;//shell脚本
失败返回 -1
*/
#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(system("ls -l") == -1){
printf("execl failed!\n");
}
printf("after execl\n");
return 0;
}
popen (可获得返回结果)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
FILE *fd ;
fd = popen("ls -l");
char readBuf[1024] = {0};
fread(readBuf,1024,1,fd);
printf("%s\r\n",readBuf);
return 0;
}