kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

setterm 防止黑屏

https://unix.stackexchange.com/questions/8056/disable-screen-blanking-on-text-console

防止黑屏

1
2
3
setterm -blank 0 -powersave off

# cat /sys/module/kernel/parameters/consoleblank

名字

setterm - 设置终端属性

概要

setterm [选项]

描述

setterm向终端写一个字符串到标准输出,调用终端的特定功能。在虚拟终端上使用,将会改变虚拟终端的输出特性。不支持的选项将被忽略。

选项

对于布尔选项(on或off),默认设置为on。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
   简洁8色如下:黑色,红色,绿色,黄色,蓝色,洋红色,青色,或白色
   black, red, green, yellow, blue, magenta, cyan, or white.
   16色是8色加上灰度或明暗,在红色、绿色、黄色、蓝色、洋红色、青色或白色之后加上灰度或明暗
   red, green, yellow, blue, magenta, cyan, or white + grey 或 bright
   各种颜色选项可以独立设置,其中设置多个模式的结果(例如,下划线和-半明亮)是硬件相关的。
   -term 终端名字
        覆盖环境变量TERM.
   -reset 显示终端重置字符串,它通常将终端重新设置为电源的状态??(测试未见任何效果)
   -initialize 清空屏幕。
   -cursor [on|off] 显示或关闭光标(测试时,没有效果)
   -repeat [on|off] 只在虚拟主机上有效:键盘打开或关闭(测试时,显示不支持)
   -appcursorkeys [on|off] 只在虚拟主机上有效
        将光标键应用程序模式设置为on或off. 
   -linewrap [on|off] (virtual consoles only)
        自动换行或关闭。
   -default:将终端的呈现选项设置为默认值。
   -foreground 8-color|default 设置前景文本颜色
   -background 8-color|default 设置背景文本颜色。
   -ulcolor 16-color (virtual consoles only)为加下划线的字符设置颜色。
   -hbcolor 16-color (virtual consoles only)设置半明字符的颜色。
   -inversescreen [on|off] (virtual consoles only)颠倒的屏幕颜色。前台和后台交换,下划线和半亮交换。
   -bold [on|off] 打开或关闭粗体(额外亮度)模式
   -half-bright [on|off]将昏暗(半亮度)模式开启或关闭
   -blink [on|off]开启或关闭闪烁模式
   -reverse [on|off]打开或关闭反向视频模式,字符和字符背景交换颜色(-inversescreen是全屏交换)
   -underline [on|off]在开启或关闭状态下显示下划线模式
   -store 存储终端当前的呈现选项
   -clear all:同命令clear
   -clear rest:测试时报参数错误
   -tabs [tab1 tab2 tab3 ...] 不带参数,测试结果如下。带参数没效果。
            root@myzr:~# setterm -tabs
            10        20        30        40        50        60        70
            12345678901234567890123456789012345678901234567890123456789012345678901234567890
            T      T       T       T       T       T       T       T       T       T      T
   -clrtabs [tab1 tab2 tab3 ...] 测试时报终端不支持:setterm: terminal xterm does not support --clrtabs
   -regtabs [1-160] 测试时报终端不支持:setterm: terminal xterm does not support --regtabs
   -blank [0-60|force|poke] 设置不活动的时间间隔,在几分钟内,之后屏幕将自动变白(如果可用的话,使用APM)
        force:即使按键被按下,也要保持屏幕空白。
        poke:开启屏幕
   -dump [1-NR_CONS]  将给定虚拟控制台(带有属性)的快照写入-file选项中指定的文件,覆盖该文件,默认文件是screen.dump
   -append [1-NR_CONS] 类似-dump,但是将其附加到快照文件,而不是重写它。
   -file dump文件名
   -msg [on|off] 启用或禁用发送内核printk()消息到控制台。
   -msglevel 1-8 设置内核打印等级。
   -powersave on|vsync 将监视器放入VESA vsync挂起模式。测试无效
   -powersave hsync 将监视器放入VESA hsync挂起模式。测试无效
   -powersave powerdown 将监视器放入VESA关闭模式。测试无效
   -powersave [off]节能模式。测试无效
   -powerdown [0-60]测试无效
   -blength [0-2000]:以毫秒为间隔设置钟的持续时间,没有参数,默认是0。测试时不支持
   -bfreq [freqnumber] 将钟频率设置为赫兹,没有参数,默认是0。测试时不支持
   -version 输出版本信息
   -help  输出帮助信息

timeout 命令

命令简介

运行指定的命令,如果在指定时间后仍在运行,则杀死该进程。用来控制程序运行的时间。

使用方法

1
2
3
timeout [选项] 数字[后缀] 命令 [参数]...

后缀 s 代表秒(默认值), m 代表分, h 代表小时, d 代表天。

选项详解

长选项必须使用的参数对于短选项时也是必需使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  -s, --signal=信号
        指定在超时时发送的信号。信号可以是类似"HUP"的信号名或是信号数。
        查看"kill -l"以获得信号列表
      --help        显示此帮助信息并退出
      --version        显示版本信息并退出
``

如果程序超时则退出状态数为124,否则返回程序退出状态。

如果没有指定信号则默认为TERM 信号。TERM 信号在进程没有捕获此信号时杀死进程。

对于另一些进程可能需要使用KILL (9)信号,当然此信号不能被捕获。

#### 示例
timeout 10 top

``` 解释:如过top命令在10秒内结束,则平安结束,运行超过10秒,将被强行kill掉。

进程通信--pipe管道

Linux函数原型

1
2
3
#include <unistd.h>

int pipe(int filedes[2]);

filedes[0]用于读出数据,读取时必须关闭写入端,即close(filedes[1]);

filedes[1]用于写入数据,写入时必须关闭读取端,即close(filedes[0])。

程序实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <unistd.h>

#define MAXLINE   20
int main(void)
{
	int n;
	int fd[2];
	pid_t pid;
	char line[MAXLINE];

	if (pipe(fd) < 0) {                /* 先建立管道得到一对文件描述符 */
		return -1;
	}

	if ((pid = fork()) < 0)                /* 父进程把文件描述符复制给子进程 */
		return -2;
	else if(pid > 0) {             /* 父进程写 */
		close(fd[0]);             /* 关闭读描述符 */
		write(fd[1], "\nhello world\n", 14);
	} else {                          /* 子进程读 */
		close(fd[1]);             /* 关闭写端 */
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	}

	return 0;
}

进程通信--消息队列

https://blog.csdn.net/lh2016rocky/article/details/70256844

消息队列提供了一种由一个进程向另一个进程发送块数据的方法。另外,每一个数据块被看作有一个类型,而接收进程可以独立接收具有不同类型的数据块。消息队列的好处在于我们几乎可以完全避免同步问题,并且可以通过发送消息屏蔽有名管道的问题。更好的是,我们可以使用某些紧急方式发送消息。坏处在于,与管道类似,在每一个数据块上有一个最大尺寸限制,同时在系统中所有消息队列上的块尺寸上也有一个最大尺寸限制。

尽管有这些限制,但是X/Open规范并没有定义这些限制的具体值,除了指出超过这些尺寸是某些消息队列功能失败的原因。Linux系统有两个定义,MSGMAX与MSGMNB,这分别定义单个消息与一个队列的最大尺寸。这些宏定义在其他系统上也许并不相同,甚至也许就不存在。

消息队列函数定义如下:

1
2
3
4
5
6
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

与信息号和共享内存一样,头文件sys/types.h与sys/ipc.h通常也是需要的。

msgget

我们可以使用msgget函数创建与访问一个消息队列:

1
int msgget(key_t key, int msgflg);

与其他IPC工具类似,程序必须提供一个指定一个特定消息队列的key值。特殊值IPC_PRIVATE创建一个私有队列,这在理论上只可以为当前进程所访问。与信息量和共享内存一样,在某些Linux系统上,消息队列并不是私有的。因为私有队列用处较少,因而这并不是一个严重问题。与前面一样,第二个参数,msgflg,由9个权限标记组成。要创建一个新的消息队列,由IPC_CREAT特殊位必须与其他的权限位进行或操作。设置IPC_CREAT标记与指定一个已存在的消息队列并不是错误。如果消息队列已经存在,IPC_CREAT标记只是简单的被忽略。

如果成功,msgget函数会返回一个正数作为队列标识符,如果失败则会返回-1。

msgsnd

msgsnd函数允许我们将消息添加到消息队列:

1
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

消息结构由两种方式来限定。第一,他必须小于系统限制,第二,必须以long int开始,这在接收函数中会用作一个消息类型。当我们在使用消息时,最好是以如下形式来定义我们的消息结构:

1
2
3
4
struct my_message {
	long int message_type;
	/* The data you wish to transfer */
}

因为message_type用于消息接收,所以我们不能简单的忽略他。我们必须定义我们自己的数据结构来包含并对其进行初始化,从而他可以包含一个可知的值。

第一个参数,msgid,是由msgget函数所返回的消息队列标识符。

第二个参数,msg_ptr,是一个指向要发送消息的指针,正如前面所描述的,这个消息必须以long int类型开始。

第三个参数,msg_sz,是由msg_ptr所指向的消息的尺寸。这个尺寸必须不包含long int消息类型。

第四个参数,msgflg,控制如果当前消息队列已满或是达到了队列消息的系统限制时如何处理。如果msgflg标记设置了IPC_NOWAIT,函数就会立即返回而不发送消息,并且返回值为-1。如果msgflg标记清除了IPC_NOWAIT标记,发送进程就会被挂起,等待队列中有可用的空间。

如果成功,函数会返回0,如果失败,则会返回-1。如果调用成功,系统就会复制一份消息数据并将其放入消息队列中。

msgrcv

msgrcv函数由一个消息队列中收取消息:

1
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

第一个参数,msqid,是由msgget函数所返回的消息队列标记符。

第二个参数,msg_ptr,是一个指向将要接收消息的指针,正如在msgsnd函数中所描述的,这个消息必须以long int类型开始。

第三个参数,msg_sz,是由msg_ptr所指向的消息的尺寸,并不包含long int消息类型。

第四个参数,msgtype,是一个long int类型,允许一个接收优先级形式的实现。如果msgtype的值为0,队列中第一个可用的消息就会被接收。如果其值大于0,具有相同消息类型的第一个消息就会被接收。如果其值小于0,第一个具有相同类型或是小于msgtype绝对值的消息就会被接收。

这听起来要比实际操作复杂得多。如果我们只是简单的希望以其发送的顺序来接收消息,我们可以将msgtype设置为0。如果我们希望接收特殊消息类型的消息,我们可以将msgtype设置为等于这个值。如果我们希望接收消息类型为n或是小于n的值,我们可以将msgtype设置为-n。

第五个参数,msgflg,控制当没有合适类型的消息正在等待被接收时如何处理。如果在msgflg中设置了IPC_NOWAIT位,调用就会立即返回,而返回值为-1。如果msgflg标记中消除了IPC_NOWAIT位,进程就会被挂起,等待一个合适类型的消息到来。

如果成功,msgrcv会返回放入接收缓冲区中的字节数,消息会被拷贝到由msg_ptr所指向的用户分配缓冲区中,而数据就会由消息队列中删除。如果失败则会返回-1。

msgctl

最后一个消息队列函数是msgctl,这与共享内存中的控制函数十分类似。

1
int msgctl(int msqid, int command, struct msqid_ds *buf);

msqid_ds结构至少包含下列成员:

1
2
3
4
5
struct msqid_ds {
	uid_t msg_perm.uid;
	uid_t msg_perm.gid
	mode_t msg_perm.mode;
}

第一个参数,msqid,是由msgget函数所返回的标记符。

第二个参数,command,是要执行的动作。他可以取下面三个值:

命令 描述
IPC_STAT 设置msqid_ds结构中的数据来反射与消息队列相关联的值。
IPC_SET 如果进程有权限这样做,这个命令会设置与msqid_ds数据结构中所提供的消息队列相关联的值。
IPC_RMID 删除消息队列。

msgrecv.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define MSG_KEY   3

struct my_msg_st
{
	long int my_msg_type;
	char some_text[BUFSIZ];
};

int main()
{
	int running = 1;
	int msgid;
	struct my_msg_st some_data;
	long int msg_to_receive = 0;
	msgid = msgget(MSG_KEY, 0666|IPC_CREAT);
	if (msgid == -1) {
		fprintf(stderr,"msgget failed with error: %d\n", errno);
		return -1;
	}
	while (running) {
		if (msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_receive, 0) == -1) {
			fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
			return -2;
		}
		printf("recvmsg: %s", some_data.some_text);
		if (strncmp(some_data.some_text, "end", 3) == 0)
			running = 0;
	}
	if (msgctl(msgid, IPC_RMID, 0) == -1) {
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
		return -3;
	}
	return 0;
}

msgsend.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define MAX_TEXT 512
#define MSG_KEY   3

struct my_msg_st
{
	long int my_msg_type;
	char some_text[MAX_TEXT];
};

int main()
{
	int running = 1;
	struct my_msg_st some_data;
	int msgid;
	char buffer[BUFSIZ];
	msgid = msgget(MSG_KEY, 0666|IPC_CREAT);
	if (msgid == -1) {
		fprintf(stderr,"msgget failed with errno: %d\n", errno);
		return -1;
	}
	while (running) {
		printf("Enter some text: ");
		fgets(buffer, BUFSIZ, stdin);
		printf("You wrote: %s", buffer);

		some_data.my_msg_type = 1;
		strcpy(some_data.some_text, buffer);
		if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1) {
			fprintf(stderr, "msgsnd failed\n");
			return -2;
		}
		if (strncmp(some_data.some_text, "end", 3) == 0)
			running = 0;
	}
	if (msgctl(msgid, IPC_RMID, 0) == -1) {
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
		return -3;
	}
	return 0;
}

信号量内核源码

https://blog.csdn.net/u012603457/article/details/52971894

之前的一片博客介绍了用于Linux内核同步的自旋锁,即使用自旋锁来保护共享资源,今天介绍另外一种Linux内核同步机制——信号量。信号量在内核中的使用非常广泛,用于对各种共享资源的保护。信号量与自旋锁的实现机制是不一样的,用处也是不一样的。首先,自旋锁和信号量都使用了计数器来表示允许同时访问共享资源的最大进程数,但自旋锁的共享计数值是1,也就是说任意时刻只有一个进程在共享代码区运行;信号量却允许使用大于1的共享计数,即共享资源允许被多个不同的进程同时访问,当然,信号量的计数器也能设为1,这时信号量也称为互斥量。其次,自旋锁用于保护短时间能够完成操作的共享资源,使用期间不允许进程睡眠和进程切换;信号量常用于暂时无法获取的共享资源,如果获取失败则进程进入不可中断的睡眠状态,只能由释放资源的进程来唤醒。最后,自旋锁可以用于中断服务程序之中;信号量不能在中断服务程序中使用,因为中断服务程序是不允许进程睡眠的。关于信号量的基本知识已经讲解完毕,接下来看看信号量在内核里面的实现,本文讲解的内核版本是linux-2.6.24。

1 数据结构

1
2
3
4
5
struct semaphore {
	atomic_t count;
	int sleepers;
	wait_queue_head_t wait;
};

信号量使用的数据结构是struct semaphore,包含三个数据成员:count是共享计数值、sleepers是等待当前信号量进入睡眠的进程个数、wait是当前信号量的等待队列。

2 信号量使用

使用信号量之前要进行初始化,其实只是简单的设置共享计数和等待队列,睡眠进程数一开始是0。本文重点讲解信号量的使用和实现。信号量操作的API:

1
2
static inline void down(struct semaphore * sem)//获取信号量,获取失败则进入睡眠状态
static inline void up(struct semaphore * sem)//释放信号量,并唤醒等待队列中的第一个进程

信号量的使用方式如下:

1
2
3
down(sem);
...临界区...
up(sem);

内核保证正在访问临界区的进程数小于或等于初始化的共享计数值,获取信号量失败的进程将进入不可中断的睡眠状态,在信号量的等待队列中进行等待。当进程释放信号量的时候就会唤醒等待队列中的第一个进程。

3 信号量的实现

3.1 down(sem)

首先看函数的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline void down(struct semaphore * sem)
{
	might_sleep();
	__asm__ __volatile__(
		"# atomic down operation\n\t"
		LOCK_PREFIX "decl %0\n\t"  /* --sem->count */
		"jns 2f\n"
		"\tlea %0,%%eax\n\t"
		"call __down_failed\n"
		"2:"
		:"+m" (sem->count)
		:
		:"memory","ax");
}

这里面包含了一些汇编代码,%0代表sem->count。也就是说先将sem->count减1,LOCK_PREFIX表示执行这条指令时将总线锁住,保证减1操作是原子的。减1之后如果大于或等于0就转到标号2处执行,也就跳过了down_failed函数直接到函数尾部并返回,成功获取信号量;否则减1之后sem->count小于0则顺序执行后面的down_failed函数。接下来看__down_failed函数的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ENTRY(__down_failed)
	CFI_STARTPROC
	FRAME
	pushl %edx
	CFI_ADJUST_CFA_OFFSET 4
	CFI_REL_OFFSET edx,0
	pushl %ecx
	CFI_ADJUST_CFA_OFFSET 4
	CFI_REL_OFFSET ecx,0
	call __down
	popl %ecx
	CFI_ADJUST_CFA_OFFSET -4
	CFI_RESTORE ecx
	popl %edx
	CFI_ADJUST_CFA_OFFSET -4
	CFI_RESTORE edx
	ENDFRAME
	ret
	CFI_ENDPROC
	END(__down_failed)

pushl和popl是用于保存和恢复寄存器的,CFI前缀的指令用于指令对齐调整。重点在函数__down,下面来看该函数的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
fastcall void __sched __down(struct semaphore * sem)
{
	struct task_struct *tsk = current;
	DECLARE_WAITQUEUE(wait, tsk);
	unsigned long flags;

	tsk->state = TASK_UNINTERRUPTIBLE;
	spin_lock_irqsave(&sem->wait.lock, flags);
	add_wait_queue_exclusive_locked(&sem->wait, &wait);

	sem->sleepers++;
	for (;;) {
		int sleepers = sem->sleepers;

		/*
		 * Add "everybody else" into it. They aren't
		 * playing, because we own the spinlock in
		 * the wait_queue_head.
		 */
		if (!atomic_add_negative(sleepers - 1, &sem->count)) {
			sem->sleepers = 0;
			break;
		}
		sem->sleepers = 1;  /* us - see -1 above */
		spin_unlock_irqrestore(&sem->wait.lock, flags);

		schedule();

		spin_lock_irqsave(&sem->wait.lock, flags);
		tsk->state = TASK_UNINTERRUPTIBLE;
	}
	remove_wait_queue_locked(&sem->wait, &wait);
	wake_up_locked(&sem->wait);
	spin_unlock_irqrestore(&sem->wait.lock, flags);
	tsk->state = TASK_RUNNING;
}

fastcall表示一种快速调用方式,函数的前两个参数由寄存器ecx和edx来传递,其余参数仍使用堆栈传递。首先将进程设为不可中断睡眠状态,即不能通过信号来唤醒,只能是内核亲自唤醒。同时将进程的TASK_EXCLUSIVE标志设为1,则wake_up()只会唤醒等待队列中的第一个进程。然后将睡眠等待数加1,之后进入for循环。函数atomic_add_negative(sleepers - 1, &sem->count)将相当于sem->count += sleepers-1,然后返回sem->count,通过该函数进行信号量获取情况测试,返回结果为0则获取资源,小于0则没有获取。这段代码使用sleepers和sem->count共同表示当前资源的使用情况。进入for循环后有两种情况,一种是atomic_add_negative执行结果为0,即获取了信号量,此时将sleepers设为0并退出循环,同时唤醒等待队列的第一个进程进行信号量获取测试;另一种是没有获取信号量,将sleepers设为1并运行schedule()进入睡眠,被唤醒之后继续执行for循环进行信号量获取测试。

注意,运行完执行一遍for指令后sleepers的值有两种结果,一种是0,一种是1。如果0则表示有一个进程通过了信号量获取的测试,则atomic_add_negative(sleepers - 1, &sem->count)实际上是将sem->count执行了减1操作,这个操作会在下一个进程进行信号量获取测试的时候执行。如果是1则表示进程没有通过信号领获取的测试,则atomic_add_negative(sleepers - 1, &sem->count)操作不会影响sem->count的值。也就是说,当进程进入__down时,sleepers只会有两个值,一个是0,一个是1。0表示之前的进程获取了信号量,1表示之前的进程没有获取信号量。如果之前进程获取了信号量,执行atomic_add_negative(sleepers - 1, &sem->count)时就会将sem->count的值减1;否则sem->count的值将保持不变。但是这个减1操作延迟到了下一个进程的执行期间,考虑到获取信号量之后进程会唤醒等待队列里的第一个进程,这个减1操作应该会很快就得到执行。

细心地小伙伴可能会注意到,首次获取信号量失败的进程不是会执行sem->sleepers++操作吗,这样不就改变了sem->count的值了吗?仔细回想获取信号量的过程,获取失败的时候会执行sem->count–操作的,因此刚好和sem->sleeper++相互呼应,结果就是不会改变sem->count的结果。即只有进程获取信号量后才会对sem->count进行减1操作,这个操作并不是马上执行,而是后续进程进行信号量获取检测的时候进行的

3.2 up(sem)

先看函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
static inline void up(struct semaphore * sem)
{
	__asm__ __volatile__(
		"# atomic up operation\n\t"
		LOCK_PREFIX "incl %0\n\t"  /* ++sem->count */
		"jg 1f\n\t"
		"lea %0,%%eax\n\t"
		"call __up_wakeup\n"
		"1:"
		:"+m" (sem->count)
		:
		:"memory","ax");
}

首先将sem->count加1,是原子操作,如果加1后sem->count大于0则说明没有进程在等待信号量资源,无须唤醒队列中进程,直接跳转到标号1处返回;否则运行__up_wakeup唤醒等待队列中的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ENTRY(__up_wakeup)
	CFI_STARTPROC
	FRAME
	pushl %edx
	CFI_ADJUST_CFA_OFFSET 4
	CFI_REL_OFFSET edx,0
	pushl %ecx
	CFI_ADJUST_CFA_OFFSET 4
	CFI_REL_OFFSET ecx,0
	call __up
	popl %ecx
	CFI_ADJUST_CFA_OFFSET -4
	CFI_RESTORE ecx
	popl %edx
	CFI_ADJUST_CFA_OFFSET -4
	CFI_RESTORE edx
	ENDFRAME
	ret
	CFI_ENDPROC
	END(__up_wakeup)

同样,我们只关注函数__up的定义:

1
2
3
4
fastcall void __up(struct semaphore *sem)
{
	wake_up(&sem->wait);
}

可以看到,__up的的工作就是唤醒等待队列中的所有进程,但是由于sem等待队列中的进程 的TASK_EXCLUSIVE标志为 1,因此不会唤醒后续进程了。也就是说up(sem)操作实际上是将sem->count自增1,然后唤醒等待队列中的第一个进程(如果有的话)。 4 小结 信号量作为一种基础的内核同步机制,使用非常广泛。本文基于linux-2.6.24内核版本介绍了信号量使用的数据结构和实现机制,同时介绍了信号量与自旋锁的区别。