程序是静态的概念,gcc xxx.c -o pro,在磁盘中生成pro文件,叫做程序
进程是程序的一次运行活动,通俗点意思就是程序跑起来了,系统中就多了一个进程。
a、使用ps指令查看
实际工作中,配合grep来查找程序中是否存在某一个进程
b、使用top指令查看,类似windows任务管理器
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
pid=0:称为交换进程(作用是进程调度)
pid=1:init进程(作用是系统初始化)
编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符
#include
#include
#include
int main()
{
pid_t pid;
pid = getpid();
printf("my pid is %d\n",pid);
return 0;
}
进程A创建了进程B
那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系
在父进程中,fork()之后,创建一个新进程,为子进程
#include
#include
#include
int main()
{
pid_t pid;
pid_t pid2;
pid = getpid();
printf("before fork:pid= %d\n",pid);
fork();
//下面会被执行两次
pid2 = getpid();
printf("after fork:pid = %d\n",pid2);
if(pid == pid2)
{
printf("this is father print\n");
}
else{
printf("this is child print,child pid = %d\n",getpid());//fork()创建新进程,pid会和刚开始的不同
}
return 0;
}
#include
#include
#include
int main()
{
pid_t pid;
pid = fork();
if(pid >0)//fork后,父进程返回值为子进程的进程ID
{
printf("this is father print,fork_retvalue = %d,pid = %d\n",pid,getpid());
}
//fork后,子进程返回值为0
else{
printf("this is child print,fork_retvalue = %d,pid = %d\n",pid,getpid());
}
while(1);
return 0;
}
#include
#include
#include
int main()
{
pid_t pid;
pid_t pid2;
pid_t retpid;
pid = getpid();
printf("before fork:pid = %d\n",pid);
retpid = fork();
pid2 = getpid();
printf("after fork:pid = %d\n",pid2);
if(pid == pid2)
{
printf("this is father print,fork_retvalue = %d\n", retpid);
}
else{
printf("this is child print,fork_retvalue = %d\n", retpid);
}
while(1);
return 0;
}
输出结果:
早期的Linux,调用fork函数以后,是把整个程序的存储空间进行拷贝。后面Linux内核的技术更新,为了提高效率,是以写时拷贝的方式各自拥有一份数据。子进程复制父进程的数据段,栈和堆,父子进程共享正文段。也就是说,对于程序中的数据,子进程要复制一份,但是对于指令,子进程并不复制,而是和父进程共享。(如果子进程对数据段中的变量不做修改时,则采用共享的原则,只有子进程对数据段中的变量进行修改的时候,才会在子进程的地址空间复制一份)
#include
#include
#include
#include
int main()
{
pid_t pid;
int data = 10;
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());
data = data + 100;
}
printf("data = %d\n",data);
return 0;
}
#include
#include
#include
int main()
{
pid_t pid;
int data;
while(1){
printf("plese input data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid ==0){
while(1){
printf("child pid is %d\n",getpid());
sleep(1);
}
}
}else{
printf("data is not 1 \n");
}
}
return 0;
}
vfork函数也可以创建进程,与fork有什么区别:
1、vfork直接使用父进程存储空间,不拷贝
2、vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
测试代码:
#include
#include
#include
#include
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0)
{
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid=%d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is child print,pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
输出结果:
可以看出,子进程是先执行的,调用exit退出后,父进程才执行
正常退出
1、Main调用return
2、进程调用函数用exit(),标准c库
3、进程调用_exit()或者_Exit(),属于系统调用
4、进程最后一个线程返回
5、最后一个线程调用pthread_exit
异常退出
1、调用abort
2、当进程收到某些信号时,如ctrl+c
3、最后一个线程对取消(cancellation)请求做出响应
status参数
它是一个整形数指针
非空:子进程退出状态放在它所指向的地址中
空:不关心退出状态
wait与waitpid的区别:
wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞
如果其所有子进程都还在运行,则阻塞
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
如果它没有任何子进程,则立即出错返回
对于waitpid函数中pid参数的作用解释如下:
pid==-1 等待任一子进程,这一方面,wait与waitpid等效
pid>0 等待其进程ID与pid相等的子进程
pid==0 等待其组ID等于调用进程组ID的任一子进程
pid<-1 等待其组ID等于pid绝对值的任一子进程
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;//初始化status值
pid = fork();
if(pid > 0)
{
wait(&status);//等待子进程结束,收到子进程的退出状态
printf("child quit.child status = %d\n",WEXITSTATUS(status));//打印子进程exit的返回值
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid=%d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is child print,pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(6);//子进程退出
}
}
}
return 0;
}
~
孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程
测试代码
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;//初始化status值
pid = fork();
if(pid > 0)
{
printf("this is father print,pid=%d\n",getpid());
sleep(1);
}
else if(pid == 0){
while(1){
printf("this is child print,child's pid = %d,father's pid = %d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 3){
exit(6);//子进程退出
}
}
}
return 0;
}
输出结果:
可以看出init进程收留孤儿进程,变成孤儿进程的父进程
函数族:
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
#include
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[]);
返回值:
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
带l的一类exac函数(l表示list),包括execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。
//文件execl.c
#include
#include
#include
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("./echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
//文件echoarg.c
#include
int main(int argc,char *argv[])
{
int i = 0;
for(i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n",i,argv[i]);
}
return 0;
}
1、入门Linux系统编程–文件
2、入门Linux系统编程–进程
3、入门Linux系统编程–线程
4、入门Linux系统编程–进程间通信
5、入门Linux系统编程–网络编程