进程

UNIX编程手册第6 7章完结 24 25 26 27 28

未完待续,可能等到期末考试结束吧

基础知识

进程号是唯一标识进程的正数,数据类型是pid_t,程序和进程号没有固定关系,init进程的pid_t总是1

#include <unistd.h>

pid_t getpid(void);  // 返回该进程的进程号
pid_t getppid(void); // 返回该进程父进程的进程号

Linux限制进程号不大于32767(可以使用cat /proc/sys/kernel/pid_max来查询此Linux内核支持的最大进程号),每一次创建新进程,内核按顺序分配进程号,当进程号到达最大时,内核重置进程号分配器,重置为300,因为300内有大量Linux守护进程和系统进程。

内存分布

进程分配进入内存时分成很多段,可以使用size命令查看二进制文件的文本段,初始化数据段,非初始化数据段。

  • 文本段

    • 程序的机器语言,只读,可以被多个进程执行
  • 初始化数据段

    • 显式初始化的全局变量和静态变量
  • 未初始化数据段(BBS段)

    • 未显式初始化的全局变量和静态变量

    • 初始化和未初始化数据段分开存储,这样程序就不需要存储未初始化的数据

      因为这些数据可以等到程序加载时再处理,所以不必占用磁盘空间

    • 运行时的数据

命令行参数

int main(int argc, char **argv)

命令行参数由Shell解析,argv[0]是程序本身,argv[argc]NULL

环境列表

获得环境

全局变量char **environ存储了环境列表,其组织形式类似argv

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> extern char **environ; int main(int argc, char *argv[])
{
char **ep; for (ep = environ; *ep != NULL; ep++)
puts(*ep); exit(EXIT_SUCCESS);
}

可以用main函数第三个参数来访问环境列表,因此上面的代码可以这样写

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> int main(int argc, char **argv, char **envp)
{
char **ep; for (ep = envp; *ep != NULL; ep++)
puts(*ep); exit(EXIT_SUCCESS);
}

使用getenv函数检索环境变量中的值,若不存在该环境变量则返回NULL。

#include <stdlib.h>

char *getenv(const char *name)

使用该函数的警示:由于函数返回字符串的指针,所以调用者有能力修改他,这是危险的,但是我的设备上没有影响哎

/**
* 危险的程序
* export say_hello="hello world"
* echo ${say_hello}
**/ #include <stdio.h>
#include <stdlib.h> int main(int argc, char **argv)
{
char *ep = NULL;
ep = getenv("say_hello"); if (ep != NULL)
{
printf("i change the first char with #\n");
ep[0] = '#';
printf("%s\n", getenv("say_hello"));
}
else
{
printf("cound not find\n");
}
exit(EXIT_SUCCESS);
} /**
* echo ${say_hello} # 仍然输出正确
**/

修改环境

仅仅是临时性的(对于该进程和其子进程有效)修改,如要永久修改,需要对文件操作。

int putenv(char *string);

设置环境变量的指针指向这个string,所以string参数绝对不能是栈中的变量

非本地跳转

goto语句只能实现函数内的跳转,不支持函数间的跳转,尤其是其他函数希望“调用”main函数,不能通过函数的返回来实现(因为返回main后将继续顺序执行而不是从头执行,所以非本地跳转解决的是函数间任意位置的跳转问题(但是这就像goto一样,滥用会导致程序逻辑混乱)。

#include <setjmp.h>

int setjmp(jmp_buf env);

void longjmp(jmp_buf env, int val);

程序逻辑:首先设置setjump函数规定跳转的落地位置和环境,程序自然执行setjmp,此时设置进程环境到env(其中也设置了程序计数寄存器栈指针寄存器,用于longjmp调用时信息)并返回0,程序调用某一函数,函数中longjmp根据传入的env返回到对应的setjmp中(基于程序计数寄存器和栈指针寄存器),同时设置val用于表明这次跳转的来源。

内存分配

在堆上分配内存malloc()

void *malloc(size_t size);

malloc函数返回的内存块支持大多数硬件架构的对齐方式,通常基于8对齐或者16对齐的,调用malloc(0)在linux下的行为是创建可以用free释放的内存。调用时若调用成功则增加program break的位置,否则输出时会返回NULL并设置errno。

#include <stdio.h>
#include <stdlib.h> int main(int argc, char **argv)
{
int *a = NULL;
a = (int*)malloc(0);
if(a == NULL)
{
printf("still null\n");
}
else
{
printf("have a space\n"); // 在linux下输出这个
free(a);
}
return 0;
}

释放内存free()

void free(int *ptr);

free函数仅仅释放空间,并不对指针变量设置为NULL,free不改变program break的位置,只是将这个空闲空间加到内存空闲内存列表中,供后续malloc使用。free(NULL)不会产生错误,所以这样的代码没有问题(把NULL加到空闲列表不会有错)。但是对一个已经free却没有设置为NULL的指针进行二次free时,free在尝试将这个内存块放入空闲内存块表中时会出现错误,导致未知问题。

这是一个测试的代码

/**
* 验证free空指针没有问题
* 验证free的调用时先将这个内存定位空
* 再将这个空内存空间放在这个内存表中
* */
#include <stdio.h>
#include <stdlib.h> int main(int argc, char **argv)
{
int *a = NULL;
a = (int*)malloc(sizeof(int) * 10);
free(a);
if(a == NULL)
{
printf("a is null\n");
free(a);
printf("free a null is safe\n");
}
else
{
a = NULL;
free(a);
printf("a is null after set and free a null is safe\n");
}
return 0;
}

其他分配内存函数

void *calloc(size_t __nmemb, size_t __size);
void *realloc(void *ptr, size_t size);

后者是对调整内存块的大小,通常是增加,ptr是需要调整空间的指针,size是大小,如果成功,返回调整后的指针(说明这里的指针不止指向内存块的地址,可能还包含内存块大小的信息),否则返回NULL,realloc(prt, 0)等价于free,realloc(NULL, size)等价于malloc。

newptr = realloc(prt, newsize);

if(nptr == NULL)
{
/* 处理错误 */
}
else
{
ptr = newptr;
}

内存对齐函数memalign()

分配内存时,起始地址要求的对齐

void *memalign(size_t alignment, size_t size);

alignment指明对齐要求,size指明大小,但alignment等于8时等价于malloc,这个函数用于设备级别,一个havefun代码

/**
* 学习使用memalign函数,探索对齐要求的用法
* 这里按照256对齐,所以地址显示最后两位是00
* */ #include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <alloca.h> int main(int argc, char **argv)
{
int i = 0;
char *a = NULL, *b = NULL, *c = NULL;
a = (char*)malloc(21);
b = (char*)memalign(256, 20);
c = (char*)malloc(20);
for(i = 0; i < 5; i++)
printf("%lx\n", (long)&a[i]);
printf("\n");
for(i = 0; i < 5; i++)
printf("%lx\n", (long)&b[i]);
printf("\n");
for(i = 0; i < 5; i++)
printf("%lx\n", (long)&c[i]);
return 0;
}
dwr@ali-s:~/Workspace/C/UNIX manual/7$ ./memalign 564bcd58a2a0
564bcd58a2a1
564bcd58a2a2
564bcd58a2a3
564bcd58a2a4 564bcd58a300
564bcd58a301
564bcd58a302
564bcd58a303
564bcd58a304 564bcd58a410
564bcd58a411
564bcd58a412
564bcd58a413
564bcd58a414
int posix_memalign(void **memptr, size_t alignment, size_t size);
void *alloca(size_t size);

在栈帧上分配内存,和malloc从堆分配内存不同,该函数不能使用free来释放内存,也不能用*alloc类函数进行二次处理,销毁由他创建的内存只需要函数返回即可。

alloca优点:

  1. alloca函数被编译器作为内联函数处理,直接调整栈指针来实现,因此不需要维护空闲内存表,执行速度快于malloc
  2. alloca函数分配的内存不需要专门释放,随栈指针移除而释放,即函数返回
  3. 在使用longjmp等非局部跳转函数时,malloc使用不慎会导致内存泄漏(有时甚至不可避免),但alloca不会

进程的创建

fork函数

#include <unistd.h>

pid_t fork();

返回

  • 0:子进程
  • -1:错误
  • pid:父进程得到子进程pid

fork函数的工作

  1. 为子进程分配性内存和内核数据结构,复制程序文本段
  2. 复制原来的进程上下文(栈段、堆段和数据段)给新进程(意味着对于某些数据是共享的,如文件描述符(内核基于dup函数实现)
    1. 子进程继承了父进程的信号处理方式
  3. 在进程集添加子进程
  4. fork执行结束,返回给两个进程
  5. 两进程独立执行,顺序取决于调度

fork函数通常的行文

#include <unistd.h>

int main(int argc, char **argv)
{
pid_t child_pid; switch (child_pid = fork())
{
case -1:
perror("fork wrong:")
break;
case 0:
/* child action */
break;
default:
/* parent action */
break;
}
return 0;
}

文件的共享

父子进程的文件描述符指向相同的文件,任意进程对文件属性(如偏移量)的修改都会影响到其他进程(举例:相同的标准输出和标准输入)

内存语义

不是简单是复制数据段(因为fork完后往往会exec系列函数调用,也就是内核进行的数据复制几乎成了浪费),而是使用小聪明对一些情况进行优化:

  • 内核将进程代码段设为只读,fork时只是创建页表项指向父进程的物理内存页帧
  • 数据段采用写时复制,只有某一进程对共享的内存数据进行写操作时,才会复制。

fork引发的竞争问题

论述了一大堆,结果就是谁先谁后依赖linux版本和/proc/sys/kernel/sched_child_run_first专有文件设置。

进程的执行

exec系列

int execv(const char*path, const char*arg0, ...);
int execl(const char*path, const char*argv[]);
int execvp(const char*path, const char*arg0, ...);
int execlp(const char*path, const char*argv[]);
  • path指明可执行程序文件的路径
  • arg指明执行程序的参数

调用成功则没有返回值,否则返回-1。

区别:

  • 以v结尾的表示参数作为vector整体传入,以l结尾的表示逐个列举的方式
  • 没有p结尾的要求指明可执行文件的绝对路径,以p结尾的可不写绝对路径,因为会优先从$PATH中查找程序

进程的监控

wait系列

pid_t wait(int * status);

阻塞的形式等待一个子进程结束,返回结束的子进程的pid并保存返回的状态,不存在子进程则返回-1

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

具体的形式等待一个子进程结束,返回结束的子进程的pid并保存返回的状态,不存在子进程则返回-1

  • pid = -1:等价于wait,等待任意一个子进程结束

  • pid = > 0:等待进程IDpid的子进程结束

  • pid = 0:等待进程组IDpid组中任意一个子进程结束

  • pid < -1:等待进程组IDpid绝对值中任意一个子进程结束

  • 0 阻塞父进程,同wait

  • WNOHANG

  • WUNTRACE

状态监测宏

进程的终止

exit()

#include <unistd.h>

void exit(int status);

将所有资源归还内核,status表示进程退出状态,可以由父进程wait函数获得

Linux C 进程的更多相关文章

  1. linux管理进程的链表

    linux2.6.11的内核中,为了方便管理linux的进程,主要建了5种linux链表.每个链表节点之间的互联有两种方式,一种是hash节点之间的互联,通过hlist_node的数据结构来实现:另一 ...

  2. [转载]了解Linux的进程与线程

    本文转自Tim Yang的博客http://timyang.net/linux/linux-process/ .对于理解Linux的进程与线程非常有帮助.支持原创.尊重原创,分享知识! 上周碰到部署在 ...

  3. Linux任务调度进程crontab的使用方法和注意事项

    参考文章:Linux任务调度进程crond命令的使用方法和注意事项 一.crond简介 概念 crond的概念和crontab是不可分割的.crontab是一个命令,常见于Unix和类Unix的操作系 ...

  4. Linux 利用进程打开的文件描述符(/proc)恢复被误删文件

    Linux 利用进程打开的文件描述符(/proc)恢复被误删文件 在 windows 上删除文件时,如果文件还在使用中,会提示一个错误:但是在 linux 上删除文件时,无论文件是否在使用中,甚至是还 ...

  5. linux 下进程通讯详解

    linux 下进程通讯方法主要有以下六种: 1.管道 2.信号 3.共享内存 4.消息队列 5.信号量 6.socket

  6. .NET跨平台实践:用C#开发Linux守护进程

    Linux守护进程(Daemon)是Linux的后台服务进程,它脱离了与控制终端的关联,直接由Linux init进程管理其生命周期,即使你关闭了控制台,daemon也能在后台正常工作. 一句话,为L ...

  7. .NET跨平台实践:用C#开发Linux守护进程(转)

    Linux守护进程(Daemon)是Linux的后台服务进程,它脱离了与控制终端的关联,直接由Linux init进程管理其生命周期,即使你关闭了控制台,daemon也能在后台正常工作. 一句话,为L ...

  8. [转]❲阮一峰❳Linux 守护进程的启动方法

    ❲阮一峰❳Linux 守护进程的启动方法 "守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问题的由来 ...

  9. Server Develop (七) Linux 守护进程

    守护进程 守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程常常在系统引导装 ...

  10. Linux 守护进程和超级守护进程(xinetd)

    一 .Linux守护进程 Linux 服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台的守护进程来执行的 ...

随机推荐

  1. IdentityServer4+OAuth2.0+OpenId Connect 详解

    一  Oauth 2.0 1 定义 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. ...

  2. Git 简介与仓库使用

    1. Git 简介 2. 远程仓库的使用 3. 本地仓库的使用 1. Git 简介 Git 是分布式版本控制系统,同一个 Git 仓库,可以分布到不同的机器上. 其原理是首先找一台电脑充当服务器的角色 ...

  3. 博客之初体验-----python初了解

    ---恢复内容开始--- 1.python2.x与python3.x的区别 (1) 2.x的默认编码是ASSIC码,不支持中文 (2) 3.x的默认编码是UNICODE,支持中文 (3) 2.x版本与 ...

  4. 文件上传bypass jsp内容检测的一些方法

    bx2=冰蝎2 前段时间渗透遇到了个检测jsp内容的,然后发现全unicode编码就可以绕过,但是对bx2马进行全编码他出现了一些错误,我尝试简单改了下,日站还是bx2操作舒服点 检测内容的话,这样直 ...

  5. 8.switch语句

    switch语句语法 switch(expression){ case value : //语句 break; //可选 case value : //语句 break; //可选 //你可以有任意数 ...

  6. POJ1151基本的扫描线求面积

    题意:      给定n个矩形的对角坐标,分别是左下和右上,浮点型,求矩形覆盖的面积. 思路:       基本的线段树扫描线求面积,没有坑点,不解释了,提示一点,有的题尤其是线段树扫描线的题需要离散 ...

  7. UVA11021麻球繁衍

    题意:      有K只麻球,每只生存一天就会死亡,每只麻球在死之前有可能生下一些麻球,生i个麻球的概率是pi,问m天后所有的麻球都死亡的概率是多少? 思路:       涉及到全概率公式,因为麻球的 ...

  8. Node-Web模块

    创建服务端------------------------------------------------------ var http = require('http'); var fs = req ...

  9. 【微信小程序】--bindtap参数传递,配合wx.previewImage实现多张缩略图预览

    本文为原创随笔,纯属个人理解.如有错误,欢迎指出. 如需转载请注明出处 在微信小程序中预览图片分为 a.预览本地相册中的图片. b.预览某个wxml中的多张图片. 分析:实质其实是一样的.都是给wx. ...

  10. GDI编程基础

    窗口和视口 视口是基于设备的采用的是设备坐标(单位:像素),窗口是基于程序的采用的是逻辑坐标(单位:像素/毫米/厘米等). 在默认的映射模式下,视口是与窗口等同的.但是如果改变其映射模式,则其对应的单 ...