进程

 

fork的调用(很重要,要理解多进程的调度)

 

 

pid_t fork(void);

返回值:

如果失败返回-1, 同时errno被设置。

如果成功:

 父进程返回  子进程的pid (>0)

 子进程返回 0

 

注意:进程自身的pid号和返回的pid号是两回事,自身pid是1002但是由于为子进程返回的也只能是0;

 

思考题:

pid1 = fork();

pid2 = fork();

printf("pid1=%d pid2=%d\n",pid1,pid2);

 

1)一共有多少个进程运行?(4个进程)

2)如果其中有一个输出是:pid1=1001,pid2=1002,那其他输出是什么?答案如上图;

1001 1002

1001 0

0 1003

0 0//一定要理解这道题;

    孤儿进程和僵尸进程

 

 

 

 

进程内存的布局(面试常考)

 

#include <stdio.h>

#include <stdlib.h>

int a = 5;// .data

 

int b;  //.bss

 

static int c = 6;  //.data

 

char* f()

{

char *p1 = "ABCDE"; //.text

 

return p1;

}

 

 

 

 void g()

 {

  char *p3 = malloc(1024);   

 }

 

int main()

{

char ch;   //栈

 

char *p = "12345"; //p局部变量都是放在栈,后面12345 放在text

 

 

*(p+1) = 'B';  //因为p所指向的是一个只读区域,

p[1] = 'B';   //和上面也是一样的问题,不能修改只读的代码段

 

p = "789";   //这个是没问题的,p是一个指针变量,这个指针可以指向其他地方。

 

p = f();

 

printf("%s\n", p);

 

*(p+1) = '1';  //常量是不能修改的。

 

char *p2 = malloc(1024);//申请一片内存区域

 

 

*p2 = 'A' ; //对申请到的内存区域做一个写的操作。

*(p2+128) = 'B';

 

 

p2 = "ABCF";  //这样做也是可以的,刚才申请的内存,就会泄露,没有free掉。

 

char ccc[] ={"abcde"};  //这样也是可以的,

 

 

ccc[1] = 'B'; //可以的,修改数组里面的内容

ccc = "abcde"; //数组名是一个常量指针,所以不能把这个指针指向其他地方。

 

printf("hello world\n");

 

}

/*只能对字符数组元素的赋值,而不能用赋值语句对整个数组赋值,如:

[cpp] view plain copy 

  1. char str4[10];  
  2. str4={'H','e','l','l','o'};    // 错误  
  3. str4="Hello";                  // 错误  
  4. str4[0]='H';str4[1]='e';str4[2]='l';str4[3]='l';str4[4]='o';   // 正确  

*/

 

 

进程内存的描述

 

 

进程状态的切换

1、程序

程序=数据结构+算法

 

程序写好之后,怎样变成一个可执行的程序?

1)预处理  gcc -E hello.c  -o hello.i

2)编译:检查语法的错误,生成对应的汇编代码                      gcc -S hello.i  -o hello.s

3)汇编:生成对应的目标文件( .o ) 生成对应的二进制文件         gcc -c hello.s  -o hello.o

4)链接:生成对应的可执行程序  gcc -o hello   hello.o

 

file hello 查看文件的属性

size hello 可以查看这个可执行程序有哪些段,和段的大小。(代码存放和进程空间布局一一对应)

gec@ubuntu:~/xjm/7.23$ size  hello //注意是size可执行文件

    text    data     bss     dec     hex filename

    1106  280   4    1390     56e hello

 

2、进程

 

进程与程序的区别

(1) 程序是静态的概念(是指令的有序集合,“程序文件”);进程是动态的概念

   (动态产生,动态消亡)

(2) 进程是一个独立的活动单位

进程是竞争系统资源的基本单位

(3) 一个程序可以对应多个进程

 

 

引入进程,是为了能让程序并发执行,进程是如何做到的呢?

进程是如何让程序并发执行的  

程序并发,实际上进程的并发

 

进程如何并发?

 

 

进程的状态:

可运行

可中断的等待状态

不可中断的等待状态

僵死

暂停

换入换出(和硬盘做一个数据的交换)

 

 

linux进程地址空间布局

linux对进程的数据进行分段管理,不同的数据,存储在不同的“内存段”中。

不同的“内存段”属性及管理方法不一样。

 

 

 

.text:

主要存放代码。

只读并且共享,这段内存在程序运行期间,不会释放。

 

 

.data:

数据段。

主要存放的程序的已经初始化的全局变量和已初始化的static变量。

可读可写,这段内存在程序运行期间,一直存在。

 

 

.bss

数据段。

主要存放在程序的未初始化的全局变量和未初始化的static变量。

可读可写,这段内存在程序运行期间,一直存在。

.bss段,在进程初始化时,(可能)全部初始化0.

 

 

.rodata

只读数据段。

主要存放程序中的只读数据(比如,字符串常量)。

只读。这段内存在程序运行期间,一直存在。

 

 

 

栈空间(stack):

栈!!!!先进后出,

主要存放局部变量。

可读可写,这段空间,会自动释放(代码块执行完了,代码块中的局部变量的

空间就会自动释放!!!)

 

堆(heap):

主要存malloc/realloc动态分配的空间。

可读可写,这段内存在程序运行期间,一旦分配,就一直存在,

直到你手动free!!!

 

 

创建一个新进程

fork

 

NAME

       fork - create a child process

 

SYNOPSIS

       #include <unistd.h>

 

fork用来 创建一个新进程,你要创建一个进程,首先你得知道

一个进程里面包含什么东西?

数据(用户数据与系统数据) 和 指令

 

你创建一个新进程时,新进程的 数据 和 指令 来源于哪里 ???

来源于  父进程(调用fork的进程,我们称之为父进程), 新创建的进程我们

称之为子进程。

子进程的数据和指令 来源于 父进程

 

fork这个函数在创建子进程时:

copy了父进程的数据和指令!!!!!!!!!!!

父进程变量,数据对象;

标准IO缓冲区

文件描述符

……。

 

copy完了后,就父子进程就独立啦!!!!

 

fork成功时,就会两个进程在执行当前的代码!!!!!!!!!!!!!

所以为了区分父子进程,fork调用一次有两次返回,在fork函数内部实现,

如果成功,就会两个进程执行当前代码,为了区分到底是父进程,还是子进程,

所以fork有两次返回,一次是父进程返回,一次是子进程返回,并且返回值不一样!!!

 

 

fork后面的语句,就会有两个进程在执行,那么谁先执行呢???????????

这个得看任务调度。不一定是父进程先运行

   

       pid_t fork(void);

 

返回值:

如果失败返回-1, 同时errno被设置。

如果成功:

 父进程返回  子进程的pid (>0)

 子进程返回 0

 

 

 

linux系统会为每一个进程,分配一个唯一的进程id(>0的整数),用类型pid_t(unsigned long)

而且还提供两个函数获取进程以及父进程的pid

getpid :用于获取自己的进程id

getppid() :用于获取父亲的进程id

 

NAME

   getpid, getppid - get process identification

 

SYNOPSIS

   #include <sys/types.h>

   #include <unistd.h>

 

   pid_t getpid(void);

   pid_t getppid(void);

 

 

 

我们一个程序运行起来之后,就是一个进程,这个进程又可以生成一个进程,这个被生成的进程叫子进程,前面这个进程叫父进程。

 

 

 

思考题:

pid1 = fork();

pid2 = fork();

printf("pid1=%d pid2=%d\n",pid1,pid2);

 

1)一共有多少个进程运行?

2)如果其中有一个输出是:pid1=1001,pid2=1002,那其他输出是什么?

 

 

 

pid_t vfork

vfork也是用来产生一个子进程,但是父进程要等子进程退出之后,才能运行,父进程被阻塞

说明vfork是共享的方式共享对应的代码段数据段,

 

 

进程的退出:

exit(0);  清空I/O缓冲区之后,才退出进程 ,在多进程里面,进程正常退出需要用exit(0);

_exit(0); 直接结束进程,不做清理的操作,直接进入到内核。

return 0; return是c语言里面的一个关键字。如果是在调用函数的时候,是出栈的处理。如果是在main函数里面,是结束进程。

 

 

僵尸进程:就是子进程退出,父进程没有处理,没有等待子进程的退出。导致子进程的资源没有被回收,子进程处于一个不可被调度被使用的状态。占据了进程ID,和相关的资源。

zombie

 

如何避免僵尸进程?需要等待子进程返回

NAME

       wait, waitpid, waitid - wait for process to change state

 

SYNOPSIS

       #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);

                       /* This is the glibc and POSIX interface; see

                          NOTES for information on the raw system call. */

 

 

这几个函数的作用,就是用来接受子进程退出的信息,无论它是怎么样退出,通过这几个函数用来释放分配给子进程的资源。如果不释放,就会变成僵尸进程。

 

wait这个函数,是阻塞型的函数,一直等待子进程返回

waitpid 可以阻塞也可以不阻塞 WNOHANG     return immediately if no child has exited. 无论有没有子进程返回,这个函数都立刻返回。

 

pid_t waitpid(pid_t pid, int *status, int options); 参数内的pid,指的是我们需要等待它返回的这个子进程的pid第二个参数,可以为NULL,第三个参数:不阻塞->WNOHANG 阻塞->0

 

 

NAME

   wait, waitpid, waitid - wait for process to change state

 

SYNOPSIS

   #include <sys/types.h>

   #include <sys/wait.h>

 

   pid_t wait(int *status);

 

   pid_t waitpid(pid_t pid, int *status, int options);

   

   这两个函数用来等待某个(些)子进程的状态发生改变的,等待的状态改变有三种情况:

(1) 子进程退出(正常退出): main函数返回/exit/_exit

(2) 子进程被信号中止

//(3) 子进程被信号唤醒

 

在子进程正常退出(1)的情况,调用wait可以释放子进程的资源,假如

没调用wait,那么子进程退出后,就会变成僵shi进程(zombie)

 

假如一个子进程的状态已经发生改变,那么调用wait/waitpid就会立即返回。

否则会阻塞调用进程直到某个子进程状态发生改变或被信号中断。

 

wait用来等待任意一个子进程退出。

pid_t wait(int *status);

status: int型,用来保存子进程的退出信息的(怎么死的,退出码等等)

返回值:

成功返回退出的那个子进程的进程id

失败返回-1,同时errno被设置。

 

status用来保存退出的子进程的状态信息的,状态信息保存在一个整数中,

我们可以用下面的这些宏来解析它的信息:

WIFEXITED(status)

return true 假如该子进程是正常退出的(main返回/exit/_exit)

WEXITSTATUS(status)

返回子进程的退出码,只有在子进程正常

退出(WIFEXITED(status) == true)时,这个宏才有意义

 

把 进程的退出码   unsigned char 来看待!!!!

 

 

WIFSIGNALED(status)

return true 假如子进程是被信号干掉的

 

……

 

pid_t waitpid(pid_t pid, int *status, int options);

pid: 指定要等待的进程或进程组

pid == -1, 表示等待任意子进程

pid == 0, 表示等待与调用进程同组的任意子进程

 

"进程组":

就是一组进程!!!每个进程就会属于某一个进程组,

并且每个进程组,都有一个组长,进程组有一个组id,

这个组id,就是组长进程的id.

pid < -1 表示等待组id 等于 pid绝对值的那个组的任意子进程

 

pid > 0 表示等待指定的子进程(其进程号为pid)

status: 同wait一样

 

options: 选项

0: 表示阻塞等待

WNOHANG: 非阻塞,假如没有子进程退出,则立即返回。

 

返回值:

成功返回退出的那个子进程的进程id

失败返回-1,同时errno被设置。

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

 

int main()

{

pid_t pid,pid1;

int status;

 

pid = fork();

 

if(pid < 0)

{

perror("fork error ");

exit(-1);

}

else if(pid == 0)

{

printf("I'am a child process pid=%d ppid=%d \n",getpid(),getppid());

sleep(5);

exit(0);

}else{

// pid1 = wait(&status);

do

{

 

pid1 = waitpid(pid,NULL,0);//WNOHANG 表示不阻塞进程的运行,如果是用这种方式,那么当子进程没有退出的时候,pid1返回的是0去,知道子进程退出了,回收函数返回的才是子进程pid号;

//如果为0,阻塞进程的运行,那么等待回收函数返回的直接是pid号;(运行程序看现象)

if(pid1 == 0)

{

printf("waiting for the child process exit\n");

sleep(1);

}

}while(pid1 == 0);

 

if(pid1 == pid)

{

printf("Now get child process exit %d\n",pid);

exit(0);

}

else{

exit(-1);

}

printf("I'am a parent process\n");

sleep(30);

}

return 0;

}

 

 

用kill命令可以杀死子进程,kill(pid,SIGKILL);

头文件:#include<signal.h>

 

 

 

 

 

  1. exec函数组

       #include <unistd.h>

 

就是用来启动另外的一个程序,让另外一个程序的代码段和数据段,覆盖当前进程的代码段和数据段。

1)要启动另外一个程序,那另外这个程序叫什么名字?

2)启动这个程序,需要一些什么参数?

 

l:list  列表,把参数一一列表写在参数里面,写完之后,需要加一个NULL。

v:vector 向量、数组,把参数写进去。

char *argv[] = { "ls" ,"-l", NULL};

 

execl execle  execlp

这个l表示参数的传递为逐个列表的方式显示。

 

execv  execve execvp

这个v代表使用向量数组的方式,来传递。

 

最后一个e,可传递的新进程的环境变量。 (环境)查看环境变量的命令:env

最后一个p,path路径。

 

这个函数最终会调用execve这个函数,,其他五个函数都是库函数。

 

返回值?需要检查他们的返回值,当返回-1的时候,表示出错,如果说成功了呢?没有返回,直接执行新的程序。

Ls -l查看当前目录下所有文件的状态信息;

Ps -ef 查看系统所有的进程;

Which 寻找命令的位置;

execl("/bin/ls","ls","-l",NULL);//重点记住一条

which命令:which ps判断ps命令的位置,得到路径/bin/ps

execl("/bin/ps","ps","-ef",NULL);//重点记住一条

只要一个参数也可以,执行ps命令的功能;

 

作业 编写一个多进程,在其中一个子进程里面,运行"ls -l"的命令,另外一个子进程睡眠5s钟,异常退出,父进程不阻塞自己,然后等待子进程退出的信息,

当接收到子进程退出的信息之后,父进程返回并且打印相关的信息。

 

#include<stdio.h>

#include<stdlib.h>//exit(0)要用到这个库头文件;

#include<unistd.h>

int main()

{

pid_t pid1,pid2,pid3,pid4;

int i=0;

pid1=fork();

pid2=fork();

if(pid1<0||pid2<0)

{

perror("fork error");

exit(-1);

}

if(pid1==0&&pid2>0)

{

 

printf("this is the first child process\n");

execl("/bin/ls","ls","-l",NULL);

// sleep(5);

exit(0);

}

if(pid2==0&&pid1>0)

{

printf("this is the second child process\n");

sleep(5);

exit(-1);

}

if(pid1!=0&&pid2!=0)

{

do

{

pid3=waitpid(pid1,0,WNOHANG);

if(pid3==0)

{

printf("waiting for the child1\n");

sleep(1);

}

}while(pid3==0);

if(pid3==pid1)

{

printf("now get child1 sucess =%d\n",pid1);

// exit(0);

}

 

do

{

pid4=waitpid(pid2,0,WNOHANG);

if(pid4==0)

{

printf("waiting for the child2\n");

sleep(1);

}

}while(pid4==0);

if(pid4==pid2)

{

printf("now get child2  sucess =%d\n",pid2);

exit(0);

}

 

}

 

return 0;

}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值