UNIX系统编程小结(三)----进程相关

简介:
     进程即一个程序的动态执行。引用apue上的一句话:"A thorough understanding of the UNIX System's process control is essential for advanced programming".
一.总述
         1.进程的开始
         在C语言中,进程是由一个main函数开始。
        
int main(int argc,char *argv[])
         我们可以向程序传入参数,以字符串数组的形式存储在argv,同时argc记录传入参数的个数。而且还可以在main的第三个参数中传入环境变量的数组,但一般不这样做。用全局environ更好。
         2.进程中
         在一个正在执行的进程中,可以fork出子进程(copy 出一个进程),也可以vfork出子进程(与新的进程share地址空间),同时父进程需要wait子进程,否则子进程就会变成僵尸进程,无家可归。我们还可以用exec function进行替换程序(但进程ID不变)。在一个进程中,system()可以执行一个shell命令,但要注意system对set-user-ID会造成权限的传递,这一点在后面会详细叙述。
         3.进程结束
        进程的结束有很多种方式,大致可以分为正常结束与异常结束。具体的我就不赘述了。主要解释exit,_exit,_Exit的区别。_exit,_Exit在结束的时候不会fllush缓冲,而exit在结束时会fllush缓冲,而且在某些UNIX系统中,exit还会关闭标准I/O流,以及与标准I/O流相关联的STDOUT_FILENO与STDIN_FILENO文件描述符。在这样的情况下,只需要用dup先复制文件描述符就可以了。
二.system call 以及 一些知识点
         我喜欢用函数来总结知识点。
        1.atexit()。可以使用atexit()来注册若干个exit handler,参数是函数的地址。当进程结束时,就会执行每一个注册了的exit handler,即执行函数。但要注意,这个执行顺序是栈的顺序,也就是"先注册,后执行"。比如说,当我做完一件事(即exit时),我先注册的A执行funcA_1,再注册的B执行funcB_2,最后注册的A执行funcA_3。但最后的执行顺序却是反的。程序如下:
 1 #include <apue.h>
 2 
 3 static void funcA_1()
 4 {
 5     printf("funcA_1\n");
 6 }
 7 static void funcB_2()
 8 {
 9     printf("funcB_2\n");
10 }
11 static void funcA_3()
12 {
13     printf("funcA_3\n");
14 }
15 
16 int main()
17 {
18     if(atexit(funcA_1)!=0)
19         err_sys("I can't register funcA_1");
20     if(atexit(funcB_2)!=0)
21         err_sys("I can't register funcB_2");
22     if(atexit(funcA_3)!=0)
23         err_sys("I can't register funcA_3");
24     exit(0);
25 }
           执行结果如下:
?
funcA_3
funcB_2
funcA_1
   2.一个C程序在内存中主要分为4个部分:
       a.初始化了的数据区域。比如int a=1;且a是在函数之外定义的,那么a就是存储在这里的。这里的a即是C语言中的静态全局变量。
       b.非初始化数据区域。比如int b;且b是定义在所有函数之外,那么b就存储在此,即未初始化的静态全局变量,会被初始化为0或者null.
       c.栈。存储自动变量,也就是在函数之内声明的变量。还有call stack,很熟悉的东西。
       d.堆。动态内存分配。malloc尽职尽责。
       3.每个进程都有一个唯一的ID。getpid()可以得到本进程的process ID,getppid()可以得到父进程的ID,但无法得到子进程的ID,因为一个进程的父进程是唯一的,但可以有多个子进程。Process ID 0是系统进程的ID,process ID 1是init进程的ID,当一个父进程先于子进程结束时,子进程就会被init进程所继承,那么该子进程的父进程就变成了init.这一点会在后面与僵尸进程作比较。
       4.fork().这是进程控制中很核心的system call.  fork产生一个新的进程,这个进程除了代码与父进程共享(代码段在执行阶段是不会发生变化的)外,其他的都是父进程的一份copy(包括分配的内存,等等),除了继承关系外,与父进程就没什么关系了。而vfork是产生一个与父进程共享一切的子进程。
       5.wait,waitpid.在子进程exit后,需要wait才能使得子进程不变成僵尸进程。如果子进程exit后,父进程没有在wait,那么这个子进程就会变成一个僵尸进程。而子进程被init所继承的原因是其父进程提前结束,即使这个父进程没有wait也不会造成僵尸进程,因为它已经提前结束了而子进程还没有exit,所以子进程会被init所继承了。由此也可说明init进程是在wait它的所有子进程的。下面是一个形成僵尸进程的程序:
 
 1 #include <apue.h>
 2 
 3 int main()
 4 {
 5     pid_t pid;
 6     if( (pid=fork())<0 )
 7         err_sys("error fork");
 8     else if(pid==0) //child
 9         exit(1); 
10     
11     sleep(2);
12     if(system("ps -o pid,ppid,state,tty,command")<0)
13         err_sys("system() error");
14     
15     exit(0);
16 }
 
 
        以下是结果:
?
3435  3434 Z pts/0    [test] <defunct>
        这里的Z就表示这是一个僵尸进程了。  
 
    6.race condition.当fork后生成子进程后,我们并不知道是子进程先执行还是父进程先执行,这时就会产生“race”。而vfork会确保子进程先于父进程执行。
       7.exec function.这是一组函数。exec有些像替换程序的意思。当执行exec后,当前进程的代码就被exec所要执行的程序代码所替换,换句话说,就是当前进程在exec之后的语句都无效了。
       8.system().   system("data>file"),即执行一个shell命令。我很喜欢这个函数,我觉得这个函数可以很轻易就实现与shell的简单交互,在某些地方会非常有用。但要注意的是,system可能会引起一个权限的传递。比如,program1有在root权限下设置的set-user-ID,这就意味着任何用户在执行program1时都会具有superuser permission.那么,如果在program1中调用system("program2"),就会将program1的superuser permission传递给program2,而这显然是我们不愿意看到的。
三.我看书时的一些笔记与想法(有些摘自apue)
       1. Three functions terminate a program normally:_exit and _Exit,which return to the kernel immediately,and exit,which performs certain cleanup processing and then returns to the kernel.
    2.Returning an integer value from the main function is equivalent to calling exit with the same value.Thus exit(0) is the same as return (0) from the main function.
      3.Note that the only way a program is executed by the kernel is when one of the exec functions is called.
   4.Stack,where automatic variables are stored,along with information that is saved each time a function is called.
     5.We can affect the environment of only the current process and any child processed that we invoke.We cannot affect the environment of the parent process,which is often a shell.
     6.*Process ID 0* is usually the scheduler process and is often known as the swapper.No program on disk corresponds to this process,which is part of the kernel and is known as a *system process*.
     7.*Process ID 1* is usually the *init* process and is invoked by the kernel at the end of the bootstrap procedure. It is a normal user process,not a system process within the kernel ,like the swapper,although it does run with superuser privileges.
     8.fork() function is called once but returns twice.
     9.*For example,the child process gets a copy of the parent's data space,heap,and stack.Note that this is a copy for the child share the text segment. 
     10.注意,如果char buf[100],这是一个固定大小的数组,那么sizeof()就会返回其字节数100; 如果:char *str=(char *)malloc(100*sizeof(char));那么sizeof(str)就是返回指针的大小。而strlen是返回当前不为'\0'的字节数。sizeof(buf)得到的字节数是要包括'\0'的 .strlen不会包括'\0'.
     11.The standard output is line buffered if it's connected to a terminal device;otherwise,it's fully buffered. 
     12.Indeed,one characteristic of fork is that all file descriptors that are open in the parent are duplicated in the child.
     13.*file table 中包含了current file offset,所以,共享一个file table就相当于共享current file offset.这很重要*
     14.Regardless of how a process terminates,the same code in the kernel is eventually executed.This kernel code closes all the open descriptors for the process,releases the memory that it was using,and the like.
     15.在进程异常终止后,是内核,而不是进程本身来生成 结束状态 。
     16.当一个进程结束的时候,不管是正常结束还是异常终止,内核都会通知它的父进程(发送SIGCHLD信号给父进程)。
     17.wait,waitpid的一个作用就是父进程用来等待子进程,防止子进程变成僵尸进程。僵尸进程,就是在一个子进程结束(*也就是exit()。一定要注意是exit()后再去找wait()*)的时候,没有父进程在wait,那么这个进程就变成了僵尸进程。
    18.With wait(),the only real error is if the calling process has no children.(Another error return is possible,in case the function call is interrupted by a signal)。
    19.With fork,we can create new processes;and with the exec functions,we can initiate new programs.
    20.ARG_MAX is the total size of the argument list and the environment list.This value must be at least 4096 bytes on a POSIX.1 system.
    21.file descriptors的close-on-exec flag的作用是什么? Every open file descriptor in a process has a close-on-exec.If this flag is set ,the descriptor is closed across an exec() function.Otherwise,the descriptor is left open across the exec function.
    22.Only a superuser process can change the real user ID.
    23.使用exec function后,该程序所在的进程就被exec所指定的程序给替换了,原程序中exec function后面的语句就不会执行了。如果那些语句还执行了,就说明exec function的执行出现了错误。
    24.在exec function中可以使用./ 当前路径 比如:execl("./hello"......)。
    25.注意 execl("./hello",arg0,arg1,...,(char *)0);这里的arg0是该命令行的第0个参数。一定要注意,这里很容易将arg0当成该命令行的第1个参数,因为在执行命令行的时候是把程序名称当作第0个参数的。这里很容易出错.  我觉得在使用execl的时候最好将arg0赋一个不需要的值然后弃用arg0,从arg1开始用,这样与平常使用命令行的思路才一样,更不容易出错。   我觉得这与a[100],从a[1]开始使用是同样的思想。当然这也不是必需的。比如./hello是一个shell,那么就需要传入类似"sh"这样的arg0参数。视情况而定。
    26.What happens if we call system() from a set-user-ID program? Doing so is a security hole and should never be done.The system() function should never be used from a set-user-ID or a set-group-ID program.
    27.A new process record is *initialized* by the kernel for the child after a fork(),not when a new program is executed.Each accounting record is written when the process *terminates* .
    28.scanf,printf,在标准输入输出流被关闭的情况下会返回-1。
    29.uid=0 即root

本文转自NeilHappy 51CTO博客,原文链接:http://blog.51cto.com/neilhappy/1093800,如需转载请自行联系原作者
相关文章
|
14天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
71 13
|
6月前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
81 3
|
3月前
|
算法 Unix 数据安全/隐私保护
Python编程--UNIX口令破解机
Python编程--UNIX口令破解机
31 1
|
4月前
|
存储 算法 Linux
C语言 多进程编程(一)进程创建
本文详细介绍了Linux系统中的进程管理。首先,文章解释了进程的概念及其特点,强调了进程作为操作系统中独立可调度实体的重要性。文章还深入讲解了Linux下的进程管理,包括如何获取进程ID、进程地址空间、虚拟地址与物理地址的区别,以及进程状态管理和优先级设置等内容。此外,还介绍了常用进程管理命令如`ps`、`top`、`pstree`和`kill`的使用方法。最后,文章讨论了进程的创建、退出和等待机制,并展示了如何通过`fork()`、`exec`家族函数以及`wait()`和`waitpid()`函数来管理和控制进程。此外,还介绍了守护进程的创建方法。
C语言 多进程编程(一)进程创建
|
4月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
4月前
|
安全 开发者 Python
揭秘Python IPC:进程间的秘密对话,让你的系统编程更上一层楼
【9月更文挑战第8天】在系统编程中,进程间通信(IPC)是实现多进程协作的关键技术。IPC机制如管道、队列、共享内存和套接字,使进程能在独立内存空间中共享信息,提升系统并发性和灵活性。Python提供了丰富的IPC工具,如`multiprocessing.Pipe()`和`multiprocessing.Queue()`,简化了进程间通信的实现。本文将从理论到实践,详细介绍各种IPC机制的特点和应用场景,帮助开发者构建高效、可靠的多进程应用。掌握Python IPC,让系统编程更加得心应手。
43 4
|
4月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
4月前
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。
|
4月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
4月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。