进程环境
1、exit和_exit函数
_exit函数直接进入内核,exit先执行 清理流程,如关闭标准IO、执行终止程序,然后进入内核。
对于main函数,一般return终止的流程用exit来表示exit(main(argc, argv))。
一、atexit函数
程序退出时,可以通过atexit函数预置终止处理程序:int atexit(void(* func)(void)。
void myexit()
{
static int n = 0;
n++;
printf("\nmyexit handle %d times!\n", n);
main();
}
int main(int argc, char*argv[])
{
printf("hello!\n");
(void)atexit(myexit);
return 0;
}
2、进程存储空间布局
a)正文段。存储机器指令,只读且共享,一般只有一份
b)初始化数据段。存储全局变量,且全局变量有初始化
c)Bss非初始化数据段。存储未初始化的全局变量,程序执行前,内核会将该段内存置为0。
测试:
char g_iTest1;
char g_iTest2 = 2;
char g_iTest3;
root@ubuntu:/home/xie/study/test# ./test1231
1: 0x80d1128, 2:0x80cf028, 3: 0x80d1129
由上诉地址可以看出,全局变量初始化和未初始化的分开放置
d)栈。存放函数内自动变量(由系统自动分配内存和收回的变量,非static局部变量)、临时变量、函数调用者的环境信息。
e)堆。分配动态内存。
如下函数测试上诉各分段的内存位置:
char g_iTest1;
char g_iTest2 = 2;
char g_iTest3;
void myexit()
{
static char n = 0;
n++;
printf("\nmyexit addr: %p!\n", &n);
}
int main(int argc, char*argv[])
{
char n1 = 0;
static char n2;
char n3[2];
char *buf = (char *)malloc(10);
printf("hello!1: %p, 2:%p, 3: %p n1: %p n2: %p n3[0 1]: %p %p, buf[0 1]: %p %p\n", &g_iTest1, &g_iTest2, &g_iTest3, &n1, &n2, &n3[0], &n3[1], &buf[0], &buf[1]);
(void)atexit(myexit);
free(buf);
return 0;
}
root@ubuntu:/home/xie/study/test# ./test1231
hello!1: 0x80d1128, 2:0x80cf028, 3: 0x80d1129 n1: 0xbfeb8e6f n2: 0x80cf7a0 n3[0 1]: 0xbfeb8e6d 0xbfeb8e6e, buf[0 1]: 0x82696a8 0x82696a9
myexit addr: 0x80cf7a1!
可以看出
除了栈,其他段都是从低地址向高地址分配空间,而栈是自顶向下;
地址空间由小到大分别存放:初始化全局变量->静态变量->未初始化全局变量->动态内存->局部变量
3、内存管理函数
常用函数有malloc、memset、free、calloc。
进程控制
进程ID 0是调度进程,也被叫做swapper进程,是一个系统进程。
进程ID 1是init进程,该进程始终运行,负责在内核自举后启动一个unix系统,它是以超级用户权限运行的普通用户进程,不是系统进程。同时它是所有孤儿进程的父进程。
获取进程信息的函数:
创建进程的函数:pid_t fork(void)。进程创建后,子进程继续执行后续指令,同时拥有父进程的数据拷贝。
printf("parent \n");
if (fork() == 0)
{
printf("child\n");
exit(0);
}
root@ubuntu:/home/xie/Desktop/test# ./test
parent
child
root@ubuntu:/home/xie/Desktop/test# ./test >abc.txt
root@ubuntu:/home/xie/Desktop/test# more abc.txt
parent
parent
child
root@ubuntu:/home/xie/Desktop/test#
观察以上回显,可以验证标准IO在终端上是行缓存的,在非终端设备上是全缓存的。创建子进程时,父进程数据空间中的缓存也会被复制,打印的字符在子进程中重复打印了一次。
Vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,子进程调用e x e c或e x i t之前,它在父进程的空间中运行。
同时,vfork保证子进程先执行,子进程exit或exec之后,才会调度父进程。
wait函数:
pid_t wait(int *statloc) ;
pid_t waitpid(pid_t pid, int *statloc, int options) ;
示例:
printf("parent \n");
if ((pid = fork()) == 0)
{
printf("child\n");
sleep(3);
return 0;
}
int status = 0;
while ((pid = waitpid(pid, &status, 1)) == 0)
{
sleep(1);
printf("parent wait\n");
}
printf("parent finish: PID = %d, status = %x\n", pid, status);
root@ubuntu:/home/xie/study/test# ./test0101
parent
child
parent wait
parent wait
parent wait
parent finish: PID = 2436, status = 0
进程组:
每个进程除了进程ID外,还属于一个进程组。每个进程组有一个组长进程,组长进程的ID与进程组ID相等。
int setpgid(pid_t pid, pid_t pgid).
信号
信号是软件中断。它提供了一种处理异步事件的方法,但是并不可靠。
signal函数是信号机制中最基本的函数,原型:
void sigact(int signo)
{
printf("signo: %d \n", signo);
printf("getpid:%d getppid:%d getpgrp:%d \n", getpid(), getppid(), getpgrp());
}
int main(int argc, char*argv[])
{
pid_t pid = 0;
char *buf = (char *)malloc(BUFFSIZE);
printf("parent, getpid:%d getppid:%d \n", getpid(), getppid());
signal(SIGUSR1, sigact);
if ((pid = fork()) == 0)
{
printf("child, getpid:%d getppid:%d\n", getpid(), getppid());
pause();
exit(0);
}
sleep(3);
kill(pid, SIGUSR1);
wait(0);
for(;;)
{
pause();
signal(SIGUSR1, sigact);
}
free(buf);
return 0;
}
回显:
root@ubuntu:/home/xie/Desktop/test# ./test &
[1] 2476
root@ubuntu:/home/xie/Desktop/test#
parent, getpid:2476 getppid:2442
child, getpid:2477 getppid:2476
root@ubuntu:/home/xie/Desktop/test#
signo: 10
getpid:2477 getppid:2476 getpgrp:2476
root@ubuntu:/home/xie/Desktop/test# ps x | grep test
2476 pts/13 S 0:00 ./test
2479 pts/13 S+ 0:00 grep –color=auto test
root@ubuntu:/home/xie/Desktop/test# kill -USR1 2476
root@ubuntu:/home/xie/Desktop/test#
signo: 10
getpid:2476 getppid:2442 getpgrp:2476
root@ubuntu:/home/xie/Desktop/test# kill -USR1 2476
root@ubuntu:/home/xie/Desktop/test#
signo: 10
getpid:2476 getppid:2442 getpgrp:2476
可以通过设置信号处理函数为:SIG_DFL来还原对信号的响应方式,或者SIG_IGN设置忽略某信号。
时钟函数alarm:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
该函数在经过设置的秒数后,产生一个信号SIGALRM,该信号默认动作是终止进程。
void sigact(int signo)
{
alarm(3);
printf("\nsigno: %d \n", signo);
}
int main(int argc, char*argv[])
{
printf("getpid:%d\n", getpid());
signal(SIGALRM, sigact);
alarm(3);
for(;;)
pause();
return 0;
}
回显:
root@ubuntu:/home/xie/Desktop/test# ./test &
[1] 2970
root@ubuntu:/home/xie/Desktop/test# getpid:2970
signo: 14
signo: 14
signo: 14
signo: 14
signo: 14
上诉代码有一个缺陷,当只需要单次信号触发时,如果系统忙碌,时钟超时可能先于pause,这种情况下进程将一直等待。
kill与raise函数:
kill函数将信号发送给进程或进程组
int kill(pid_t pid, int signo);
pid大于0表示传递给目标进程,等于0表示有权限的进程组内所有进程。
int raise(int signo);
raise函数提供进程给自己发送信号的功能。