进程
进程环境=======
main函数
1、c程序总是main函数开始执行,main函数的原型是:
int main(int argc, char *argv[])
{}
其中,argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组,其实就是指针数组
2、命令行参数
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
for(i = 0; i < argc; i++)
{
printf("argc = %d argv[%d] = %s\n", argc, i, argv[i]);
}
return 0;
}
root@ubuntu:/home/jianghu/share# ./app
argc = 1 argv[0] = ./app
root@ubuntu:/home/jianghu/share# ./app abc efg
argc = 3 argv[0] = ./app
argc = 3 argv[1] = abc
argc = 3 argv[2] = efg
char *argv[]一个指针数组
进程终止
1、有8种方式使进程终止,其中5种为正常终止,它们是
(1)从main返回
(2)调用exit
(3)调用_exit或_Exit
(4)最后一个线程从其启动例程返回
(5)从最后一个线程调用pthread_exit
基本概括为一个进程正常终止或者进程中最后一个线程正常终止
异常终止有3中方式,它们是
(6)调用abort
(7)接到一个信号
(8)最后一个线程对取消请求做出响应
基本概括为一个进程异常终止或者进程中最后一个线程异常终止
2、退出函数
3个函数用于正常终止一个程序,_exit和_Exit立即进入内核,exit则先执行一些清理处理,然后返回内核
#incldue <stdlib.h>
void exit(int status)
void _Exit(int status)
#include <unistd.h>
void _exit(int status)
exit函数总是执行一个标准I/O库的清理关闭操作:对于所有打开流调用fclose函数,这就造成输出缓冲中的所有数据都被冲洗(写到文件上)
3个退出函数都带一个整型参数,称为终止状态(或退出状态),如果调用这些函数时不带终止状态,或main执行一个无返回值的return语句,或main没有声明返回类型为整型,则该进程的终止状态是未定义的,但是,若main的返回类型是整型,并且main执行到最后一条语句时返回,那么该进程的终止状态是0
main函数返回一个整型值与用该值调用exit是等价的,于是在main函数中exit(0)等于return(0)
#include <stdio.h>
main()
{
printf("hello world\n");
}
对该程序进行编译,然后运行,则可见到其终止码是随机的
7-2.png注意,内核使程序执行的唯一方法是调用一个exec函数,进程自愿终止的唯一方法是显式或隐式(通过调用exit)调用_exit或_Exit,进程也可非自愿地由一个信号使其终止。
c程序的存储空间布局
1、c程序一直由下列几部分组成:正文段、初始化数据段、未初始化数据段、堆、栈
正文段:这是CPU执行的机器指令部分,通常,正文段是可共享的,正文段常常是只读的,以防止程序由于意外而修改其指令。
初始化数据段:通常将此段称为数据段,它包含了程序中需明确的赋初值的变量,例如 int maxcount = 99,使此变量以其初值存放在初始化数据段中
未初始化数据段:通常将此段称为bss段,在程序开始执行之前,内核将此段中的数据初始化为0或空指针,例如函数的声明 long sum[1000]将此变量存放在非初始化数据段中
栈:自动变量以及每次函数调用时所需保存的信息都保存在此段中。每次函数调用时,其返回地址以及调用者的环境信息(如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。
堆:通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于未初始化数据段和栈之间。
7-6.png
未初始化数据段的内容并不存放在磁盘程序文件中,其原因是,内核在程序开始运行前将他们都设置为0,需要存放在磁盘程序文件中的段只有正文段和初始化数据段
存储空间分配
1、malloc,分配指定字节数的存储区,此存储区中的初始值不确定
2、calloc,为指定数量指定长度的对象分配存储空间。该空间中的每一位(bit)都初始化为0
3、realloc,增加或减少以前分配区的长度,当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
3个函数返回值:若成功,返回非空指针,若出错,返回NULL
这3个分配函数所返回的指针一定是适当对齐的,使其可用于任何数据对象
void free(void *ptr);
函数free释放ptr指向的存储空间
详见目录c中《浅谈malloc,calloc,realloc函数之间的区别》
进程控制=======
1、进程控制,主要包括进程的创建、执行和终止
2、进程标识
每个进程都有一个非负整型表示的唯一进程ID,因为进程ID标识符总是唯一的。虽然是唯一的,但是进程ID是可复用的,当一个进程终止后,其进程ID就成为复用的候选者。
ID为0 的进程通常是调度进程
#include <unistd.h>
pid_t getpid(void) //返回值:调用进程的进程ID
pid_t getppid(void) //返回值:调用进程的父进程的ID
3、函数fork
作用:一个现有的进程可以调用fork函数创建一个新进程
#include <unistd.h>
pid_t fork(void)
返回值:子进程返回0,父进程返回子进程ID,如出错,返回-1
由fork创建的新进程被称为子进程(child process),fork函数被调用一次,但返回两次,两次返回的区别是子进程的返回值是0,而父进程的返回值则是新建子进程的进程ID。
将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID
fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获取父进程的数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父进程和子进程并不共享这些存储空间部分。父进程和子进程共享正文段。
由于在fork之后经常跟随者exec,所以现在的很多实现并不执行一个父进程数据段、堆和栈的完全副本。作为替代,使用了写时复制技术。这些区域由父进程和子进程共享,而且内核将他们的访问权限改变为只读。如果父进程和子进程中的任一个试图修改这些区域,则内核只为修改区域那块内存制作一个副本。
demo
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
pid_t pid;
int a = 0, b =0;
pid = fork();
if(pid < 0)
{
printf("it is error\n");
}
else if(pid == 0)
{
a++;
b++;
printf("this is child process a = %d b = %d\n", a, b);
}
else
{
a = 2;
b = 2;
printf("this is parent process a = %d b = %d\n", a, b);
}
return 0;
}
root@ubuntu:/home/jianghu/share# ./app
this is parent process a = 2 b = 2
this is child process a = 1 b = 1
从中可以看到,子进程对变量所做的改变并不影响父进程中该变量的值
一般来说,在fork之后是父进程先执行还是子进程先执行时不确定的,这取决于内核所使用的调度算法。
文件共享(待补充)
4、函数vfork
vfork函数的调用序列和返回值与fork相同,但两者的语义不同。
vfork和fork区别
区别一、vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序。vfork与fork一样都是创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会引用该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中,如果子进程修改数据(除了用于存放vfork返回值的变量)、进行函数调用、或者没有调用exec或exit就返回都可能会带来未知的结果。
区别二、vfork保证子进程先运行,在它调用exec或exit之后父进程才能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁)
demo
#include <stdio.h>
#include <unistd.h>
#if 1
int main(int argc, char *argv[])
{
int a = 8;
pid_t pid;
pid = vfork();
if(pid < 0)
{
printf("this is error\n");
}
else if(pid == 0)
{
a++;
printf("this is child process a = %d\n", a);
_exit(0);
}
else
{
printf("this is parent process a = %d\n", a);
}
return 0;
}
#else
int main(int argc, char *argv[])
{
int a = 8;
pid_t pid;
pid = fork();
if(pid < 0)
{
printf("this is error\n");
}
else if(pid == 0)
{
a++;
printf("this is child process a = %d\n", a);
}
else
{
printf("this is parent process a = %d\n", a);
}
return 0;
}
#endif
root@ubuntu:/home/jianghu/share# ./app
this is child process a = 9
this is parent process a = 9
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号,因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理函数)。对于这种信号的系统默认动作是忽略它。