进程

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. Civil3d中 如何用管轴线的变坡点桩号控制其他纵断面数据的显示?

    如何用管轴线的变坡点桩号控制其他纵断面数据的显示?如下图所示: 主要进行两步操作,下面以地面高程为例. 第1步: 右键纵断面图,打开纵断面图特性对话框,选择"标注栏"选项卡,分别设 ...

  2. 多图详解 TCP 连接管理,太全了!!!

    TCP 是一种面向连接的单播协议,在 TCP 中,并不存在多播.广播的这种行为,因为 TCP 报文段中能明确发送方和接受方的 IP 地址. 在发送数据前,相互通信的双方(即发送方和接受方)需要建立一条 ...

  3. 群晖 创建nfs 共享文件夹 k8s 使用

    1) 打开控制面板 2) 打开共享文件夹 3) 新增共享文件夹 4) 基本信息配置 2) 3) 4) 5) 点完确定,应该会退出,继续选中刚才创建的,点编辑 2) 3) 5)返回主页面,点击file ...

  4. 基于MATLAB的手写公式识别(9)

    基于MATLAB的手写公式识别(9) 1.2图像的二值化 close all; clear all; Img=imread('drink.jpg'); %灰度化 Img_Gray=rgb2gray(I ...

  5. php 一些神奇加有趣的函数

    php里面神奇且又有趣的函数 这么有意思的title,我忍不住要啰嗦俩句,1--只是个人喜欢,不喜勿喷:2--仅个人笔记,未完,待续 列举 get_defined_constants:get_defi ...

  6. hdu4864 贪心

    题意:        给你n太机器,m个任务,每个任务和机器都有两个权值x,y,每个机器只能被一个任务使用,条件是机器的两个权值分别比任务的大于等于,每个任务获得的价值是x*500+y*2,问你最多能 ...

  7. UVA11384正整数序列(把123..变成0的最小步数)

    题意:      给定一个正整数n,你的任务是最少的操作次数把序列1 2 3 4 5...n中所有的数字都变成0,每次操作可以从序列中选择一个活多个整数,同时减去一个相同的正整数,比如 1 2 3可以 ...

  8. Day009 面向对象和方法回顾

    面向过程&面向对象 面向过程思想 步骤清晰简单,第一步做什么,第二步做什么..... 面象过程适合处理一些较为简单的问题 面向对象思想 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些 ...

  9. Filter过滤器的基本使用方法

    ProjectDescription Filter的使用 创建类实现javax.servlet.Filter. 重写方法: init(); //过滤器初始化 doFilter(); //过滤请求 1. ...

  10. Java中浮点数的坑

    基本数据类型 浮点数存在误差 浮点数有一个需要特别注意的点就是浮点数是有误差的,比如以下这段代码你觉得输出的什么结果: public class Demo { public static void m ...