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
- char str4[10];
- str4={'H','e','l','l','o'}; // 错误
- str4="Hello"; // 错误
- 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>
- 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;
}