进程篇(3: 基本进程控制)--请参照本博客“操作系统”专栏

1. 进程标识符:

每个进程都有一个非负整型表示的唯一进程ID。但进程ID可以重用,当一个进程终止之后,其进程ID就可以再次被重用了。

UNIX系统中常常有一些专用的进程:

  • ID为0的进程通常是调度进程,常常被称为交换进程(swapper),该进程是内核的一部分,它并不执行磁盘上的任何程序,因此也被称为系统进程。

  • ID为1的进程通常是init进程,在自举过程结束后由内核调用,在比较新的版本中是/sbin/init。此进程负责在自举内核后启动一个UNIX系统。init通常读取与系统有关的初始化文件,并将系统引导到一个状态!

  • ID为2的进程是页守护进程,此进程负责支持虚拟存储系统的分页操作。

Unix中返回一些与进程相关的标识符的函数:

1) 返回调用进程及其父进程的ID:


 1 SYNOPSIS
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 pid_t getpid(void);
6 pid_t getppid(void);
7
8 DESCRIPTION
9 getpid() returns the process ID of the calling process. (This is often used by routines that generate
10 unique temporary filenames.)
11
12 getppid() returns the process ID of the parent of the calling process.
13
14 ERRORS
15 These functions are always successful.

下面我们来编写一段程序返回一个程序的调用进程的id号:


 1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main(void)
6 {
7 pid_t calling_id = getpid();
8 printf("The calling process id: %d\n",calling_id);
9 return 0;
10 }

我在我的计算机上运行./a.out 三次,结果如下:

1 The calling process id: 5539
2 The calling process id: 5542
3 The calling process id: 5543

可见getpid返回的是调用 pid_t calling_id = getpid();
这段代码的进程的进程id号。因为每次调用都不会重用上一次的进程id号,所以呈现递增的趋势!

下面我们编一段代码返回调用进程的父进程id号:


 1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main(void)
6 {
7 pid_t pp_id = getppid();
8 printf("The parent id of calling process: %d\n",pp_id);
9 return 0;
10 }

我们在shell中连续运行3次./a.out,结果如下:

The parent id of calling process: 5088
The parent id of calling process: 5088
The parent id of calling process: 5088

我们用ps命令查看当前用户系统中运行的程序id:

  PID TTY          TIME CMD
2651 pts/0 00:00:00 bash
5088 pts/0 00:00:00 bash
5670 pts/0 00:00:00 ps

显然这个pp_id就是当前运行a.outd的shell进程的id。

2)返回调用程序的用户及有效用户id:


 1 SYNOPSIS
2 #include <unistd.h>
3 #include <sys/types.h>
4
5 uid_t getuid(void);
6 uid_t geteuid(void);
7
8 DESCRIPTION
9 getuid() returns the real user ID of the calling process.
10
11 geteuid() returns the effective user ID of the calling process.
12
13 ERRORS
14 These functions are always successful.

3) 返回进程的组id和有效组id:


 1 SYNOPSIS
2 #include <unistd.h>
3 #include <sys/types.h>
4
5 gid_t getgid(void);
6 gid_t getegid(void);
7
8 DESCRIPTION
9 getgid() returns the real group ID of the calling process.
10
11 getegid() returns the effective group ID of the calling process.
12
13 ERRORS
14 These functions are always successful.

2. 进程的创建,执行和结束终止:

  2.1  进程创建:

    2.1.1 fork 函数:


NAME
fork - create a child process(创建一个子进程)

SYNOPSIS
#include <unistd.h>

pid_t fork(void);

DESCRIPTION
fork() creates a new process by duplicating the calling process(复制调用进程来创建新进程). The new process, referred
to as the child, is an exact duplicate of the calling process, referred to as the parent,
except for the following points:

* The child has its own unique process ID, and this PID does not match the ID of any exist‐
ing process group (setpgid(2)).

* The child‘s parent process ID is the same as the parent‘s process ID.

* The child does not inherit its parent‘s memory locks (mlock(2), mlockall(2)).(子进程并不继承父进程的内存锁)

* Process resource utilizations (getrusage(2)) and CPU time counters (times(2)) are reset
to zero in the child.(子进程的资源使用计数和CPU时间计数都被值成0)

* The child‘s set of pending signals is initially empty (sigpending(2)).(子进程的挂起信号量的数目初始化为0)

* The child does not inherit semaphore adjustments from its parent (semop(2)).(子进程并不继承父进程的信号量调节器)

* The child does not inherit record locks from its parent (fcntl(2)).(子进程并不继承父进程的记录锁)

* The child does not inherit timers from its parent (setitimer(2), alarm(2), timer_cre‐
ate(2)).(子进程不从父进程中继承时间计数器)

* The child does not inherit outstanding asynchronous I/O operations from its parent
(aio_read(3), aio_write(3)), nor does it inherit any asynchronous I/O contexts from its
parent (see io_setup(2)). (子进程并不继承父进程的异步I/O操作和异步I/O内容)

The process attributes in the preceding list are all specified in POSIX.1-2001. The parent
and child also differ with respect to the following Linux-specific process attributes:

* The child does not inherit directory change notifications (dnotify) from its parent (see
the description of F_NOTIFY in fcntl(2)).

* The prctl(2) PR_SET_PDEATHSIG setting is reset so that the child does not receive a sig‐
nal when its parent terminates.

* The default timer slack value is set to the parent‘s current timer slack value. See the
description of PR_SET_TIMERSLACK in prctl(2).

* Memory mappings that have been marked with the madvise(2) MADV_DONTFORK flag are not
inherited across a fork().

* The termination signal of the child is always SIGCHLD (see clone(2)).

* The port access permission bits set by ioperm(2) are not inherited by the child; the
child must turn on any bits that it requires using ioperm(2).

Note the following further points:

* The child process is created with a single thread—the one that called fork(). The entire
virtual address space of the parent is replicated in the child, including the states of
mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3)
may be helpful for dealing with problems that this can cause.

* The child inherits copies of the parent‘s set of open file descriptors. Each file
descriptor in the child refers to the same open file description (see open(2)) as the
corresponding file descriptor in the parent. This means that the two descriptors share
open file status flags, current file offset, and signal-driven I/O attributes (see the
description of F_SETOWN and F_SETSIG in fcntl(2)).

* The child inherits copies of the parent‘s set of open message queue descriptors (see
mq_overview(7)). Each descriptor in the child refers to the same open message queue
description as the corresponding descriptor in the parent. This means that the two
descriptors share the same flags (mq_flags).

* The child inherits copies of the parent‘s set of open directory streams (see opendir(3)).
POSIX.1-2001 says that the corresponding directory streams in the parent and child may
share the directory stream positioning; on Linux/glibc they do not.

RETURN VALUE
On success, the PID of the child process is returned in the parent, and 0 is returned in the
child. On failure, -1 is returned in the parent, no child process is created, and errno is
set
appropriately.(返回值)

ERRORS
EAGAIN fork() cannot allocate sufficient memory to copy the parent‘s page tables and allo‐
cate a task structure for the child.

EAGAIN It was not possible to create a new process because the caller‘s RLIMIT_NPROC
resource limit was encountered. To exceed this limit, the process must have either
the CAP_SYS_ADMIN or the CAP_SYS_RESOURCE capability.

ENOMEM fork() failed to allocate the necessary kernel structures because memory is tight.

ENOSYS fork() is not supported on this platform (for example, hardware without a Memory-Man‐
agement Unit).

CONFORMING TO
SVr4, 4.3BSD, POSIX.1-2001.

NOTES
Under Linux, fork() is implemented using copy-on-write pages, so the only penalty that it
incurs is the time and memory required to duplicate the parent‘s page tables, and to create
a unique task structure for the child.

Since version 2.3.3, rather than invoking the kernel‘s fork() system call, the glibc fork()
wrapper that is provided as part of the NPTL threading implementation invokes clone(2) with
flags that provide the same effect as the traditional system call. (A call to fork() is
equivalent to a call to clone(2) specifying flags as just SIGCHLD.) The glibc wrapper
invokes any fork handlers that have been established using pthread_atfork(3).

      下面一段程序演示了fork函数,可以看出,子进程对变量所做的改变并不影响父进程中变量的值!


 1 #include "apue.h"
2
3 int glob = 6; /* external variable in initialized data */
4 char buf[] = "a write to stdout\n";
5
6 int
7 main(void)
8 {
9 int var; /* automatic variable on the stack */
10 pid_t pid;
11
12 var = 88;
13 if(write(STDOUT_FILENO,buf,sizeof(buf)-1) != sizeof(buf)-1)
14 err_sys("write error");
15 printf("before fork\n");
16
17 if((pid = fork()) < 0)
18 {
19 err_sys("fork error");
20 }
21 else if(pid == 0) /* child! */
22 {
23 glob++;
24 var++;
25 }
26 else
27 sleep(2);
28
29 printf("pid = %d, glob = %d, var = %d\n",getpid(),glob,var);
30 exit(0);
31 }

$ ./a.out > result

a write to stdout
before fork
pid = 6275, glob = 7, var = 89
before fork
pid = 6274, glob = 6, var = 88

一般来说,在调用fork函数之后,父进程和子进程的执行顺序是不确定的,这取决于内核所使用的进程调度算法!

如果要实现父子进程间的的同步,必须实现进程间的通信,这里,让父进程sleep两秒而让子进程先执行!

    

时间: 2024-08-10 21:28:40

进程篇(3: 基本进程控制)--请参照本博客“操作系统”专栏的相关文章

进程篇(4: 基本进程控制:其他相关控制)--请参照本博客“操作系统”专栏

1. 更改进程的用户ID和组ID:为什么我们要更改用户ID和组ID的呢? 在UNIX系统中,特权是基于用户和组ID的.当用户需要增加特权,或要访问某个当前没有能力访问的文件时,我们需要更改自己的权限,以让新的ID具有合适的特权或访问权限.与此类似,当程序需要降低其特权或阻止对某些资源的访问时,也需要跟换用户ID或组ID;一般而言,在设计应用程序时,我们总是试图使用"最小特权"模型.依照此模型,我们的程序应当值具有为完成特定的任务所需要的最小特权. NAME getuid, geteui

进程篇(3: 基本进程控制:进程的退出)--请参照本博客“操作系统”专栏

1. exit函数: 进程的五种正常的结束方式: 在main函数中执行return语句,这等效于exit; 调用exit函数.此函数由ISO C定义,其操作包括运行各终止处理程序,然后关闭所有标准I/O流等. 调用_exit或_Exit函数,ISO C定义了_Exit函数,目的是为了为进程提供一种无需运行终止处理程序和信号处理程序而终止的方法.并不处理标准I/O流! 进程的最后一个线程在其启动例程中执行返回语句,然后该进程以终止状态0返回. 进程的最后一个线程调用pthread_exit函数.

进程篇(1: 进程运行环境)--请参照本博客“操作系统”专栏

2014年5月30日  下午1:40:59 1. Unix 进程执行环境: 1.1 终止处理程序: ISO C 规定,一个程序可以登记多达32个函数,这些函数将由exit自动调用.我们称这些函数为终止处理程序(exit handler),并调用atexit函数来登记这些函数.该函数的原型如下: 1 #include <stdlib.h>2 3 int atexit(void (*function)(void)); exit调用这些终止程序的顺序与他们登记时的顺序相反(先登记后调用).同一个函数

进程篇(2: C程序的存储空间布局)--请参照本博客“操作系统”专栏

1.  C程序的存储空间布局: C 程序由下面几个部分组成: 正文段(即是代码段): 这是由CPU执行的机器指令部分.通常,正文段是可以共享的,并常常是可读的,以防止程序因为意外原因而修改自身的代码! 初始化数据段(即数据段): 它包含了程序中需要明确的赋初值的变量. 非初始化数据段(bss段):在程序开始执行之前,内核将此段中的数据初始化为0或空指针. 栈.自动变量以及每次函数调用时所需保存的信息都存放在此段中.每次调用函数时,返回地址以及调用者的环境信息(如某些寄存器的值)都存放在栈中.然后

总纲篇:产品结构设计指导VI(本博客指引章节)

本章目的:搭建自己的产品结构设计konw-how体系,从零开始设计一个完整产品. 需知远途即捷径! (//作者的结构设计体系尚在搭建中,所有的文章都会定期进行一定编排修改) 目录: 序言 1.基础篇: 1)时代的发展与结构设计-3d与2d的设计变化(夸夸其谈中): 2)基于特征设计概念介绍(重要):3)规范化:3d草绘.建模.装配:4)规范化:工程图出图:5)一个完整的机械产品需要哪些图纸: 6)形位公差标注(GD&T标准): 6.1)符号 Symbol 6.2)标注  Mark 6.3)基准

操作系统之进程篇(1)

1.进程介绍: 1.1 进程模型: 进程是一个程序的实际执行,包含了程序计数器的状态,寄存器和变量等等! 程序可以看成是一个状态的序列,程序在不同时刻呈现出不同的状态,而这种状态的前后交替过程可以看成是程序的执行过程.概念上来说,每个程序有自己的虚拟CPU,但在现实中CPU在不同的进程间来回切换,又称这种切换为伪并行! 进程和程序差别看似微小,实际上却是十分精妙; 可以将计算机执行程序的过程看成一次有趣的烹饪过程.食谱就是程序,厨师就是CPU,而食材是输入,得到的输出是鲜美可口的美食. 当厨师在

操作系统之进程篇(2)

进程间通信(InterProcess Communication,IPC): 进程通信中遇到的三个问题: a) 进程之间如何进行信息的传递? b) 多个进程在执行自己的核心代码时如何能够不相互影响? c) 当进程之间出现相互依赖关系时,如何才能合理的调度进程的执行顺序! 1. 竞争情形: 当两个或多个进程同时读写某个共享资源的时候,程序运行的最终结果由各个进程的具体执行的情况所决定! 如何避免竞争情形的出现,那么我们首先引入关键代码区的定义: 程序中访问共享内存或其他共享资源的代码区被称为关键代

(转)进程间关系:进程、僵尸进程、孤儿进程、进程组、前台进程组、后台进程组、孤儿进程组、会话、控制终端

不同的shell对使用管道线时创建子进程的顺序不同,本文以bash为例,它是支持作业控制的shell的典型代表. 僵尸进程与孤儿进程 僵尸进程:先于父进程终止,但是父进程没有对其进行善后处理(获取终止子进程有关信息,释放它仍占有的资源).消灭僵尸进程的唯一方法是终止其父进程.孤儿进程:该进程的父进程先于自身终止.其特点是PPID=1(init进程的ID).一个孤儿进程可以自成孤儿进程组. 文中用到的缩写 PID = 进程ID (由内核根据延迟重用算法生成)PPID = 父进程ID(只能由内核修改

操作系统之进程篇(4)--经典进程间通信(IPC)问题

1. 哲学家进餐问题: 问题描述: 五个哲学家在一个圆桌上进餐,每人的面前放了一盘意大利面,两个盘子之间有一个叉子,但是由于盘子里面的面条十分光滑,需要两个叉子才能进行就餐行为.餐桌的布局如下图所示: 假设哲学家的生活中只有两个活动:吃饭和思考[吃饭维持自身之生存,思考探究生存之意义],当然这样的哲学家在现实之中是不存在的.当一个哲学家在殚精竭虑之时,饥饿感随之而来,这是他会拿起左右手边的两个叉子来想享用这俗世之中的美味.酒足饭饱之后,又"躲进小楼成一统,管他春夏与秋冬"去了.问题是: